Skip to main content

Beanโ€‹

Bean is an object that is managed and constructed by Clawject container it can have dependencies, and it can be a dependency for other Beans.

Declare Beanโ€‹

There are a few ways to declare a Bean, let's explore all of them one by one.

@Bean decoratorโ€‹

You can decorate property, getter or method with @Bean decorator to declare a Bean.

When you're decorating method or property that holds arrow function - it's your responsibility to provide return value for bean, if it's class - it should be instantiated, if it's some dynamic value โ€” it should be returned from function provided.

Functions decorated with @Bean decorator called factory-functions.

You can provide bean dependencies via function arguments by specifying an argument type.

Declaring method and arrow function beans
class Foo {
name = 'foo';
}

@ClawjectApplication
class Application {
@Bean foo() {
return new Foo();
}

@Bean fooName = (foo: Foo) => foo.name;
}

When your bean doesn't have any dependencies - you can just assign value to property decorated with @Bean decorator.

Declaring bean without dependencies
class Foo {}

@ClawjectApplication
class Application {
@Bean foo = new Foo();
}

Bean functionโ€‹

It can be quite annoying to declare each dependency in method or arrow function for your class, especially when your class has a lot of dependencies. So, if you add/change/remove a dependency - you need to change it in factory-function level as well. To solve this inconvenience Clawject has Bean function that accepts class constructor, and automatically instantiate it with all needed dependencies.

class Bar {}
class Baz {}

class Foo {
constructor(
private dependency0: string,
private dependency1: number,
private dependency2: Bar,
private dependency3: Baz,
) {}
}

@ClawjectApplication
class Application {
foo = Bean(Foo);

@Bean stringBean = 'dependency0';
@Bean numberBean = 1;
barBean = Bean(Bar);
bazBean = Bean(Baz);
}

Bean Typesโ€‹

Bean type is a type of value that bean will provide as a dependency for other beans.

There are a few restrictions on bean value types: undefined, void, null and never types. If you specify an invalid bean type, you will get compile-time error.

@ClawjectApplication
class Application {
@Bean undefined = undefined;
@Bean void: void = undefined;
@Bean null = null;
@Bean never(): never { throw new Error('never'); }
}

Specifying a Bean typeโ€‹

You can specify the type explicitly for each Bean, but it is not necessary to, because Clawject using TypeScript type system to infer types.

The next two examples are equivalent:

class Foo {}

@ClawjectApplication
class Application {
@Bean propertyBean = new Foo();
@Bean factoryMethodBean() { return new Foo(); }
@Bean arrowFunctionBean = () => new Foo();
functionBean = Bean(Foo);
}

Bean type narrowingโ€‹

In this example we will narrow a bean type from FooBar & IFoo & IBar to IFoo:

interface IFoo {
foo: string;
}

interface IBar {
bar: string;
}

class FooBar implements IFoo, IBar {
foo = 'foo';
bar = 'bar';
}

@ClawjectApplication
class Application {
/* The type of bean is IFoo now */
fooBar = Bean<IFoo, typeof FooBar>(FooBar);

@PostConstruct
init(
/* fooBar bean will be injected */
foo: IFoo,
/* Compilation error here because no beans found with type IBar */
bar: IBar,
/* Compilation error here because no beans found with type FooBar */
fooBar: FooBar,
) {}
}

Asynchronous Beansโ€‹

Sometimes you need to create a bean asynchronously, for example, when you need to fetch some data from the server to create a bean. To mark beans as asynchronous, you can just return Promise from factory-function, async method or just plain bean value.

When Bean is requested as a dependency for another bean or as an exposed bean, Clawject will wait for the promise to resolve and then use the resolved value as a bean.

class Foo {
constructor(
/*
* We don't need to specify parameter as Promise<string>
* because Clawject will handle promises automatically
*/
private somePropertyThatFetchedAsynchronously: string,
) {}
}

@ClawjectApplication
class Application {
@Bean async somePropertyThatFetchedAsynchronously() {
const response = await fetch('https://example.com/some-data');
return response.text();
}

foo = Bean(Foo);
}

Also if you want to use dynamic import to create a bean without factory functions (using only Bean function) you can do it like this:

foo.ts
export class Foo {}
app.ts
@ClawjectApplication
class Application {
foo = Bean(import('./foo').then(module => module.Foo));
}

Bean Dependenciesโ€‹

Bean dependency is a parameter of a bean that is injected by Clawject. Dependency can be declared in class constructor, or in a factory-method.

class Foo {
constructor(
private bar: string,
private baz: number,
) {}
}

@ClawjectApplication
class Application {
@Bean bar = 'barValue';
@Bean baz = 42;

foo = Bean(Foo);
}

How dependencies are resolvedโ€‹

There are a few steps that are taken to resolve bean dependencies.

Firstly - Clawject try to find all beans that are compatible with a dependency type. If none of the compatible bean candidates are found - compilation error will be reported.

If only one compatible bean is found - it's used as a dependency.

class Foo {
constructor(
private bar: string // <- "barBean" will be injected
) {}
}

@ClawjectApplication
class Application {
@Bean barBean = 'barValue';

foo = Bean(Foo);
}

If more than one compatible bean is found - Clawject try to find a candidate that has the same name as parameter. If such a bean is found - it's used as a dependency.

class Foo {
constructor(
private bar: string // <- "bar" will be injected
) {}
}

@ClawjectApplication
class Application {
@Bean bar = 'barValue';
@Bean barOther = 'barOtherValue';

foo = Bean(Foo);
}

If more than one compatible bean is found, and none of them has the same name as parameter and not @Primary - then compilation error will be reported.

class Foo {
constructor(
private bar: string // <- compilation error
) {}
}

@ClawjectApplication
class Application {
@Bean bar1 = 'bar1Value';
@Bean bar2 = 'bar2Value';

foo = Bean(Foo);
}

Optional dependenciesโ€‹

Dependencies can be optional, if there are no suitable bean candidates found - undefined or null will be injected.

Let's take a look at how you can mark dependency as optional:

class Foo {
constructor(
private bar?: string, // <- optional dependency, `undefined` will be injected
) {}
}

@ClawjectApplication
class Application {
foo = Bean(Foo);
}

Circular dependenciesโ€‹

Clawject will detect circular dependencies and report compilation error with involved beans.

class Foo {
constructor(
private bar: Bar,
) {}
}

class Bar {
constructor(
private baz: Baz,
) {}
}

class Baz {
constructor(
private foo: Foo,
) {}
}

@ClawjectApplication
class Application {
/*
* Circular dependencies detected.
* โ”Œโ”€โ”€โ”€โ”€โ”€โ”
* | foo
* โ†‘ โ†“
* | bar
* โ†‘ โ†“
* | baz
* โ””โ”€โ”€โ”€โ”€โ”€โ”˜
*/
foo = Bean(Foo);
bar = Bean(Bar);
baz = Bean(Baz);
}