Skip to content

Commit

Permalink
docs: add missing files to fix dead links
Browse files Browse the repository at this point in the history
  • Loading branch information
xavier-zenika authored and Romakita committed Mar 8, 2024
1 parent 17cf87d commit 5068610
Show file tree
Hide file tree
Showing 3 changed files with 265 additions and 0 deletions.
74 changes: 74 additions & 0 deletions docs/docs/custom-providers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Custom providers

There are a lot of scenarios where you might want to bind something directly to the IoC container.
For example, any constant values, configuration objects created based on the current environment,
external libraries, or pre-calculated values that depend on few other defined providers.

Moreover, you are able to override default implementations, e.g. use different classes or make use of various test doubles (for testing purposes) when needed.

One essential thing that you should always keep in mind is that Ts.ED uses @@TokenProvider@@ to identify a depencency.

Usually, the auto-generated tokens are equal to classes. If you want to create a custom provider, you'd need to choose a token.
Mostly, the custom tokens are represented by either plain strings or symbols.

Let's go through the available options.

## Use Value

The `useValue` syntax is useful when it comes to either define a constant value, put external library into DI container,
or replace a real implementation with the mock object.

<<< @/docs/snippets/providers/custom-provider-use-value-declaration.ts

In order to inject custom provider, we use the @@Inject@@ decorator. This decorator takes a single argument - the token.

<<< @/docs/snippets/providers/custom-provider-use-value-usage.ts

## Use Factory

The `useFactory` is a way of creating providers dynamically.
The actual provider will be equal to a returned value of the factory function.
The factory function can either depend on several different providers or stay completely independent.
It means that factory may accept arguments, that DI will resolve and pass during the instantiation process.

<<< @/docs/snippets/providers/custom-provider-use-factory-declaration.ts

In order to inject a custom provider, we use the @@Inject@@ decorator. This decorator takes a single argument - the token.

<<< @/docs/snippets/providers/custom-provider-use-value-usage.ts

::: tip
Since v6.110.0, factory and custom provider can register his own [hooks](/docs/hooks.md)!
:::

## Use Async Factory

The `useAsyncFactory` is a way of creating asynchronous providers dynamically.
The actual provider will be equal to a returned value of the factory function.
The factory function can either depend on several different providers or stay completely independent.
It means that factory may accept arguments, that DI will resolve and pass during the instantiation process.

<<< @/docs/snippets/providers/custom-provider-use-async-factory-declaration.ts

In order to inject a custom provider, we use the @@Inject@@ decorator. This decorator takes a single argument - the token.

<<< @/docs/snippets/providers/custom-provider-use-value-usage.ts

::: warning
Async factory will always be considered as `SINGLETON`. It is not possible to use other scopes like `REQUEST` and `INSTANCE` because asynchronous providers are resolved on server loading.
:::

## Use Class

The `useClass` syntax is similar to register provider via decorator. But it allows you to use different classes per chosen factors.
For example you can change the class depending on the environment profile `production` or `development`.

<<< @/docs/snippets/providers/custom-provider-use-class-declaration.ts

::: tip
registerProvider can be used to add a provider or override an existing provider (like @@OverrideProvider@@ decorator).
:::

In this case, even if any class depends on ConfigService, Ts.ED will inject an instance of the provided class (`ConfigService` or `DevConfigService`) instead.

<<< @/docs/snippets/providers/custom-provider-use-class-usage.ts
58 changes: 58 additions & 0 deletions docs/docs/injection-scopes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Injection scopes

The scope of a [Provider](/docs/providers.md) defines the lifecycle and visibility of that bean in the context in which it is used.

Ts.ED DI defines 3 types of @@Scope@@: `singleton`, `request` and `instance` which can be used on `Provider`, `Service`, `Middleware` and `Controller`.

## Singleton scope

Singleton scope is the default behavior of all providers. That means all providers are created during server initialization.

<<< @/docs/snippets/providers/scope-singleton.ts

::: tip Note
In this example all requests on `/random` endpoint return the same random value.
:::

## Request scope

Request scope will create a new instance of provider for each incoming request. A new container will be created
and attached to the request. It will contain all providers annotated by `@Scope(ProviderScope.REQUEST)`.

<<< @/docs/snippets/providers/scope-request.ts

Each request on `/random` will return a different random value.

### Chain with Service

It is also possible to use `@Scope(ProviderScope.REQUEST)` on a service if your service is injected on a controller
which is annotated by `@Scope(ProviderScope.REQUEST)` too.

Here is a working example:

<<< @/docs/snippets/providers/scope-chain.ts

And here is an unworking example:

<<< @/docs/snippets/providers/scope-chain-fail.ts

::: warning
The `SINGLETON` annotation avoids the `@Scope(ProviderScope.REQUEST)` annotation put on MyService.
:::

::: warning
The `@Scope(ProviderScope.REQUEST)` annotation has no effect on Global middlewares.
:::

### Performance

Using request-scoped providers will obviously affect application performance.
Even though Ts.ED is trying to cache as much metadata as possible, it will still have to create an instance of your class on each request.
Hence, it will slow down your average response time and overall benchmarking result.
If your provider doesn't necessarily need to be request-scoped, you should rather stick with the singleton scope.

## Instance scope

Instance scope used on a provider tells the injector to create a new instance each time the provider is injected to another one.

<<< @/docs/snippets/providers/scope-instance.ts
133 changes: 133 additions & 0 deletions docs/docs/providers-lazy-loading.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
---
meta:
- name: description
content: Documentation over Providers / DI provided by Ts.ED framework. Use providers to build your backend services.
- name: keywords
content: providers di ioc ts.ed express typescript node.js javascript decorators jsonschema class models
---

# Lazy-loading provider

<Badge text="6.81.0+"/>

By default, modules are eagerly loaded, which means that as soon as the application loads, so do all the modules,
whether or not they are immediately necessary. While this is fine for most applications, it may become a bottleneck for
apps running in the **serverless environment**, where the startup latency `("cold start")` is crucial.

Lazy loading can help decrease bootstrap time by loading only modules required by the specific serverless function
invocation. In addition, you could also load other modules asynchronously once the serverless function is "warm" to
speed-up the bootstrap time for subsequent calls even further (deferred modules registration).

## Getting started

To load a provider on-demand, Ts.ED provide decorators @@LazyInject@@ and @@OptionalLazyInject@@. Here is an example
with a @@PlatformExceptions@@:

```typescript
import { Injectable, LazyInject } from "@tsed/di";
import type { PlatformExceptions } from "@tsed/platform-exceptions";

@Injectable()
class MyInjectable {
@LazyInject("PlatformException", () => import("@tsed/platform-exceptions"))
private platformExceptions: Promise<PlatformExceptions>;

async use() {
try {
/// do something
} catch (er) {
const exceptions = await this.platformExceptions;
platformExceptions.catch(er, {});
}
}
}
```

The LazyInject decorator will load the `node_module` and invoke automatically the `PlatformException` exported class,
only when the decorated property will be used by your code.

::: tip

Lazy-loaded provider is `cached` on the first call.
That means, each consecutive call will be very fast and will return a cached instance, instead of loading the provider again.

:::

Create you own lazy injectable doesn't require special things, just declare a module or an injectable service is enough:

```typescript
import { Module } from "@tsed/di";

@Module({
// works also with @Injectable
imports: [], // Use the imports field if you have services to build
})
export class MyModule {
$onInit() {
// The hook will be called once the module is loaded
}
}
```

Then use it:

```typescript
import { Injectable, LazyInject } from "@tsed/di";
import type { MyModule } from "../MyModule.ts";

@Injectable()
class MyInjectable {
@LazyInject("MyModule", () => import("../MyModule.ts"))
private myModule: Promise<MyModule>;

async use() {
const myModule = await this.myModule;

myModule.doSomething();
}
}
```

::: warning

If you use Webpack, make sure to update your `tsconfig.json` file - setting `compilerOptions.module` to `"esnext"` and adding `compilerOptions.moduleResolution` property with `"node"` as a value:

```json
{
"compilerOptions": {
"module": "esnext",
"moduleResolution": "node"
}
}
```

:::

## Lazy loading programmatically

Ts.ED provide also a way to lazy load a provider programmatically. You just need to inject the @@InjectorService@@ in service:

```typescript
import { Injectable, Inject, InjectorService } from "@tsed/di";

@Injectable()
class MyService {
@Inject()
protected injector: InjectorService;

async load() {
const { MyModule } = await import("../lazy/my-module.ts");
const myModule = await this.injector.lazyInvoke(MyModule);

myModule.doSomething();
}
}
```

## Limitation

Some providers cannot be lazy loaded:

- Controllers,
- Middlewares,
- All providers that need to run a specific hook (excepted `$onInit` hook).

0 comments on commit 5068610

Please sign in to comment.