Skip to content

c100k/libmodulor

Repository files navigation

libmodulor

npm version license

A TypeScript library to create platform-agnostic applications.

Warning

The project is still in active development. Although already used in pilot projects, it's not suitable for all production scenarios yet. Being developed by only one person, it may keep going for years or stop at any time. In the meantime, it's still a "research project" that needs improvement. Thus, it will be subject to BREAKING CHANGES as long as the version is not 1.0.0. All that said, the end goal is really to have a production-grade library to help everyone build quality projects faster.

🚀 Getting Started

If you're discovering libmodulor, we recommend reading the 📖 Documentation first. You'll find everything you need to get started : Concepts, Examples and Guides.

When you're ready, 🚀 Create a project and build the awesome idea you have in mind.

In the meantime, here is how to declare the four layers of libmodulor.

These snippets are extracted from the Basic example (check out the full example to get the full picture).

App

const appManifest = {
    languageCodes: ['en', 'fr'],
    name: 'Event',
    ucReg: {
        Register: {
            action: 'Create',
            icon: 'user',
            name: 'Register',
        },
    },
} satisfies AppManifest;
const appI18n: AppI18n = {
    en: {
        ucif_email_label: 'Your email address',
        ucif_firstname_label: 'Your firstname',
        ucif_lastname_label: 'Your lastname',
        ucof_id_label: 'Your registration #',
        ucof_ticketNumber_label: 'Your ticket #',
    },
    fr: {
        ucif_email_label: 'Votre adresse email',
        ucif_firstname_label: 'Votre prénom',
        ucif_lastname_label: 'Votre nom',
        ucof_id_label: "Votre # d'inscription",
        ucof_ticketNumber_label: 'Votre # de ticket',
    },
};

Use Case

interface RegisterInput extends UCInput {
    email: UCInputFieldValue<Email>;
    firstname: UCInputFieldValue<PersonFirstname>;
    lastname: UCInputFieldValue<PersonLastname>;
}

interface RegisterOPI0 extends AggregateOPI0 {
    amount: Amount;
    ticketNumber: TicketNumber;
}

@injectable()
class RegisterClientMain implements UCMain<RegisterInput, RegisterOPI0> {
    constructor(@inject('UCManager') private ucManager: UCManager) {}

    public async exec({
        uc,
    }: UCMainInput<RegisterInput, RegisterOPI0>): Promise<
        UCOutput<RegisterOPI0>
    > {
        const { aggregateId } = await this.ucManager.persist(uc);

        const amount: Amount = 99.99; // Should come from some catalog in a real application
        const ticketNumber: TicketNumber = 1; // Should come from a safely auto-generated sequence in a real application

        return new UCOutputBuilder<RegisterOPI0>()
            .add({
                amount,
                id: aggregateId,
                ticketNumber,
            })
            .get();
    }
}

const RegisterUCD: UCDef<RegisterInput, RegisterOPI0> = {
    io: {
        i: {
            fields: {
                email: {
                    type: new TEmail(),
                },
                firstname: {
                    type: new TPersonFirstname(),
                },
                lastname: {
                    type: new TPersonLastname(),
                },
            },
        },
        o: {
            parts: {
                _0: {
                    fields: {
                        ...AggregateOutputDef.parts?._0.fields,
                        amount: {
                            type: new TAmount('EUR'),
                        },
                        ticketNumber: {
                            type: new TTicketNumber(),
                        },
                    },
                    order: ['ticketNumber', 'amount', 'id'],
                },
            },
        },
    },
    lifecycle: {
        client: {
            main: RegisterClientMain,
            policy: EverybodyUCPolicy,
        },
    },
    metadata: {
        action: 'Create',
        icon: 'user',
        name: 'Register',
    },
};

Product

const productManifest: ProductManifest = {
    appReg: [{ name: 'Event' }],
    name: 'Eventer',
};
const productI18n: ProductI18n = {
    en: {
        ...I18nEN,
        ...appI18n.en,
    },
    fr: {
        ...I18nFR,
        ...appI18n.fr,
    },
};

Target

const container = new Container(CONTAINER_OPTS);

bindCommon(container);
bindNodeCore(container);
bindProduct(container, productManifest, productI18n);

👨‍💻 Contribute

If you think you can help in any way, feel free to contact me (cf. author in package.json). I'd love to chat.

⚖️ License

LGPL-3.0

About

A TypeScript library to create platform-agnostic applications

Topics

Resources

License

Stars

Watchers

Forks

Contributors 2

  •  
  •