Skip to content

Commit 4ce2627

Browse files
committed
feat: add InjectSelf decorator
update documentation
1 parent a69c275 commit 4ce2627

39 files changed

+1759
-675
lines changed

docs/app/globals.css

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/* Custom styles for redi docs */
2+
3+
/* Code block font */
4+
code,
5+
pre,
6+
pre code,
7+
.nextra-code,
8+
[data-rehype-pretty-code-figure] code,
9+
[data-rehype-pretty-code-figure] pre {
10+
font-family: 'Ioskeley Mono', 'Fira Code', 'JetBrains Mono', 'SF Mono', 'Cascadia Code', 'Menlo', 'Monaco', 'Consolas', 'Liberation Mono', 'Courier New', monospace;
11+
font-feature-settings: 'liga' 1, 'calt' 1; /* Enable ligatures */
12+
}
13+
14+
/* Inline code */
15+
:not(pre) > code {
16+
font-family: 'Ioskeley Mono','Fira Code', 'JetBrains Mono', 'SF Mono', 'Cascadia Code', 'Menlo', 'Monaco', 'Consolas', 'Liberation Mono', 'Courier New', monospace;
17+
font-size: 0.875em;
18+
}

docs/app/layout.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Footer, Layout, Navbar } from 'nextra-theme-docs';
33
import { Head } from 'nextra/components';
44
import { getPageMap } from 'nextra/page-map';
55
import 'nextra-theme-docs/style.css';
6+
import './globals.css';
67

78
export const metadata = {
89
title: {

docs/content/docs/_meta.ts

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,36 @@
11
export default {
22
introduction: 'Introduction',
33
concepts: 'Concepts',
4+
5+
'---Core': {
6+
type: 'separator',
7+
title: 'Core',
8+
},
49
identifier: 'Identifier',
5-
item: 'Dependency item',
10+
item: 'Dependency Item',
611
binding: 'Binding',
712
'declare-dependency': 'Declare Dependency',
8-
react: 'Using in React',
9-
hierarchy: 'Hierarchy Injection System',
13+
14+
'---Decorators': {
15+
type: 'separator',
16+
title: 'Decorators',
17+
},
18+
decorators: 'Overview',
19+
'with-new': '@WithNew',
20+
'inject-self': '@InjectSelf',
1021
forwardref: 'forwardRef',
11-
'with-new': '@WithNew Decorator',
22+
23+
'---Advanced': {
24+
type: 'separator',
25+
title: 'Advanced',
26+
},
27+
hierarchy: 'Hierarchy Injection',
28+
react: 'React Integration',
29+
30+
'---Other': {
31+
type: 'separator',
32+
title: 'Other',
33+
},
1234
env: 'Setup Dev Environment',
1335
faq: 'FAQ',
1436
};

docs/content/docs/binding.mdx

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,53 @@
1-
# Binding
1+
import { Callout } from "nextra/components";
22

3-
After declaring identifiers and dependencies, it is necessary to establish a binding relationship between the identifiers and dependencies, and register these bindings in the injector. The injector can only return dependencies based on identifiers when it knows a set of bindings.
3+
# Binding
44

5-
There are two ways to register bindings on the injector:
5+
A binding connects an identifier to a dependency item. The injector uses bindings to know what to provide when a dependency is requested.
66

7-
- As parameters of the injector constructor
8-
- By calling the `add` method of the injector
7+
## Registering Bindings
98

10-
## As parameters of the injector constructor
9+
### In the Constructor
1110

12-
The first parameter of the `Injector` constructor is a set of bindings. You have seen a lot of code like this in the previous section:
11+
The most common way is to pass bindings when creating the injector:
1312

1413
```ts
1514
const injector = new Injector([
16-
[SomeClass],
17-
[IdentifierA, { useClass: SomeClass }],
18-
[IdentifierB, { useValue: SomeValue }],
19-
[IdentifierC, { useFactory: SomeFactory }],
20-
[IdentifierD, { useAsync: SomeAsyncFunction }],
15+
[AuthService], // Class as both identifier and item
16+
[ILogger, { useClass: ConsoleLogger }], // Interface → Class
17+
[IConfig, { useValue: { debug: true } }], // Interface → Value
18+
[IFactory, { useFactory: () => create() }], // Interface → Factory
2119
]);
2220
```
2321

24-
## By calling the `add` method of the injector
22+
### Using the `add` Method
23+
24+
You can also add bindings later:
25+
26+
```ts
27+
const injector = new Injector();
28+
29+
injector.add([AuthService]);
30+
injector.add([ILogger, { useClass: ConsoleLogger }]);
31+
```
32+
33+
<Callout>
34+
Be careful with `add` — make sure bindings are registered before they are requested, or you'll get an error.
35+
</Callout>
36+
37+
## Modifying Bindings
38+
39+
### Replace a Binding
2540

2641
```ts
27-
injector.add([SomeClass]);
28-
injector.add([IdentifierA, { useClass: SomeClass }]);
29-
injector.add([IdentifierB, { useValue: SomeValue }]);
42+
injector.replace([ILogger, { useClass: FileLogger }]);
3043
```
3144

32-
This approach is more flexible because it does not restrict when you can register bindings. However, this type of binding can be a double-edged sword as it may not have been registered by the time you retrieve dependencies through the injector. Therefore, we recommend using the first method as a priority unless you can ensure the correct order of registration and retrieval.
45+
### Delete a Binding
3346

34-
## Replacing or deleting bindings
47+
```ts
48+
injector.delete(ILogger);
49+
```
3550

36-
Before an identifier is resolved, it is possible to replace or delete the binding of the identifier. This can be done by calling the `replace` or `delete` methods of the injector.
51+
<Callout type="warning">
52+
You can only replace or delete bindings **before** they are resolved. Once a dependency has been created, changes won't take effect.
53+
</Callout>

docs/content/docs/concepts.mdx

Lines changed: 74 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,81 @@
1-
# Conecpts
1+
import { Callout } from "nextra/components";
22

3-
A [**binding**](./binding) maps an [**identifier**](./identifier) to a [**dependency item**](./item).
3+
# Concepts
44

5-
An identifier distinguish a dependency item from others. It could be:
5+
Understanding three core concepts is key to using redi effectively.
66

7-
- a value returned from `createIdentifier`
8-
- a class
7+
## Overview
98

10-
A dependency item is an implementation matching an identifier. It could be:
9+
```
10+
┌─────────────┐ binds to ┌─────────────────┐
11+
│ Identifier │ ──────────────────▶│ Dependency Item │
12+
└─────────────┘ └─────────────────┘
13+
│ │
14+
└───────────── Binding ─────────────┘
15+
16+
17+
┌─────────────┐
18+
│ Injector │
19+
└─────────────┘
20+
```
1121

12-
- a class
13-
- a class dependency item
14-
- a js primitive type value or object
15-
- a factory function
22+
## Identifier
1623

17-
An **injector** holds a set of bindings and resolve an identifier by constructing a dependency item.
24+
An **identifier** is a unique token that distinguishes one dependency from another. It answers the question: "What do I want to inject?"
1825

19-
A dependency item can [declare what dependencies it needs](./relationship).
26+
There are two types of identifiers:
27+
28+
| Type | When to Use | Example |
29+
|------|-------------|---------|
30+
| **Class** | Simple cases where the class is the implementation | `AuthService` |
31+
| **IdentifierDecorator** | When you want to program to an interface | `createIdentifier<IAuthService>('auth')` |
32+
33+
👉 Learn more: [Identifier](/docs/identifier)
34+
35+
## Dependency Item
36+
37+
A **dependency item** is the actual implementation that will be injected. It answers the question: "What should be provided?"
38+
39+
| Type | Description | Example |
40+
|------|-------------|---------|
41+
| **Class** | A class to be instantiated | `{ useClass: AuthService }` |
42+
| **Value** | A constant value | `{ useValue: { apiUrl: '...' } }` |
43+
| **Factory** | A function that creates the dependency | `{ useFactory: () => new Service() }` |
44+
45+
👉 Learn more: [Dependency Item](/docs/item)
46+
47+
## Binding
48+
49+
A **binding** connects an identifier to a dependency item. It tells the injector: "When someone asks for X, provide Y."
50+
51+
```ts
52+
const injector = new Injector([
53+
[AuthService], // Class as both identifier and item
54+
[ILogger, { useClass: ConsoleLogger }], // Interface → Class
55+
[IConfig, { useValue: { debug: true } }], // Interface → Value
56+
]);
57+
```
58+
59+
👉 Learn more: [Binding](/docs/binding)
60+
61+
## Injector
62+
63+
The **injector** holds all bindings and resolves dependencies. When you call `injector.get(SomeService)`, it:
64+
65+
1. Finds the binding for the identifier
66+
2. Creates the dependency item (if not already created)
67+
3. Recursively resolves any nested dependencies
68+
4. Returns the instance
69+
70+
<Callout>
71+
By default, dependencies are **singletons** within an injector. The same instance is returned for subsequent requests.
72+
</Callout>
73+
74+
## What's Next?
75+
76+
Now that you understand the concepts, dive deeper into each topic:
77+
78+
1. **[Identifier](/docs/identifier)** - Learn different ways to create identifiers
79+
2. **[Dependency Item](/docs/item)** - Explore class, value, and factory items
80+
3. **[Binding](/docs/binding)** - Understand how to register bindings
81+
4. **[Declare Dependency](/docs/declare-dependency)** - Learn to declare dependencies between classes

docs/content/docs/declare-dependency.mdx

Lines changed: 33 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -2,90 +2,75 @@ import { Callout } from "nextra/components";
22

33
# Declare Dependency
44

5-
After creating identifiers and dependency items, you need to declare dependency relationship among dependencies. There are two types of dependency items that could dependent on others: class items and factory function items.
5+
Once you have identifiers and dependency items, you need to declare the relationships between them.
66

7-
## On a class
7+
## On a Class
88

9-
To declare dependencies of a class, you could use the `Injector` decorator:
9+
Use the `@Inject` decorator on constructor parameters:
1010

11-
```ts highlight=3
11+
```ts
1212
class MapService {
1313
constructor(
14-
@Inject(SatelliteService) private readonly satellite: SatelliteService,
14+
@Inject(SatelliteService) private readonly satellite: SatelliteService
1515
) {}
1616
}
1717
```
1818

19-
or an `IdentifierDecorator`:
19+
You can also use an `IdentifierDecorator` directly:
2020

21-
```ts highlight=7
21+
```ts
2222
interface IPlatformService {}
23-
2423
const IPlatformService = createIdentifier<IPlatformService>("platform");
2524

2625
class DialogService {
2726
constructor(
28-
@IPlatformService private readonly platformSrc: IPlatformService,
27+
@IPlatformService private readonly platform: IPlatformService
2928
) {}
3029
}
3130
```
3231

33-
## On a factory
32+
## On a Factory
3433

35-
You should list all its dependencies in the `deps` property:
34+
List dependencies in the `deps` array:
3635

37-
```ts highlight=9
38-
const item = [
39-
I18NNumberTranspiler,
40-
{
41-
useFactory: (i18nService: I18NService) => {
42-
return i18nService.isChinese()
43-
? new ChineseNumberTranspiler()
44-
: new EnglishNumberTranspiler();
36+
```ts
37+
const injector = new Injector([
38+
[I18NService],
39+
[
40+
IFormatter,
41+
{
42+
useFactory: (i18n: I18NService) => {
43+
return i18n.isChinese() ? new ChineseFormatter() : new EnglishFormatter();
44+
},
45+
deps: [I18NService],
4546
},
46-
deps: [I18NService],
47-
},
48-
];
47+
],
48+
]);
4949
```
5050

51-
## Optional
51+
## Optional Dependencies
5252

53-
When a dependency is not held by an injector, the injector would throw an error when you want to get that dependency.
53+
By default, missing dependencies throw an error. Use `@Optional` to allow `null`:
5454

55-
```ts highlight=3,8
55+
```ts
5656
class MapService {
5757
constructor(
58-
@Inject(SatelliteService) private readonly satellite: SatelliteService,
58+
@Optional(SatelliteService) private readonly satellite?: SatelliteService
5959
) {}
6060
}
6161

6262
const injector = new Injector([[MapService]]);
63-
injector.get(MapService); // ERROR!
63+
injector.get(MapService); // Works! satellite is undefined
6464
```
6565

66-
You could use `Optional` instead `Inject`, to mark `SatelliteService` as an optional dependency not a required one.
66+
## Multiple Instances
6767

68-
```ts highlight=3,8
69-
class MapService {
70-
constructor(
71-
@Optional(SatelliteService) private readonly satellite?: SatelliteService,
72-
) {}
73-
}
68+
Use `@Many` to inject all registered instances as an array:
7469

75-
const injector = new Injector([MapService]);
76-
injector.get(MapService);
77-
```
78-
79-
In this case, `MapService` could construct, but its property `satellite` would be undefined.
80-
81-
## Multiple
82-
83-
Similarly, you could use `Many` to mark the dependency could be injected with multiple instances:
84-
85-
```ts highlight=3,9-10
70+
```ts
8671
class MapService {
8772
constructor(
88-
@Many(ISatelliteService) private readonly satellites: ISatelliteService[],
73+
@Many(ISatelliteService) private readonly satellites: ISatelliteService[]
8974
) {}
9075
}
9176

@@ -94,5 +79,6 @@ const injector = new Injector([
9479
[ISatelliteService, { useClass: GPSSatellite }],
9580
[ISatelliteService, { useClass: BeidouSatellite }],
9681
]);
97-
injector.get(MapService);
82+
83+
// satellites array contains both GPS and Beidou instances
9884
```

0 commit comments

Comments
 (0)