Last modified by Manuel Leduc on 2026/05/18 11:53

Show last authors
1 1. Declare role symbols(((
2 Role identifiers are symbol values. Put them in a small, dependency-free module so every contributor can import them without pulling implementations.
3
4 {{code language="typescript"}}
5 // src/roles.ts
6 export const NOTIFIER_ROLE = Symbol("Notifier");
7 export const CHANNEL_ROLE = Symbol("Channel");
8 export const FORMATTER_ROLE = Symbol("Formatter");
9 {{/code}}
10 )))
11 1. Define interfaces(((
12 {{code language="typescript"}}
13 // src/types.ts
14 export interface Formatter {
15 format(message: string): string;
16 }
17
18 export interface Channel {
19 send(message: string): Promise<void>;
20 }
21
22 export interface Notifier {
23 notify(message: string): Promise<void>;
24 }
25 {{/code}}
26 )))
27 1. Implement components(((
28 {{code language="typescript"}}
29 // src/plain-formatter.ts
30 import { injectable } from "@xwiki/platform-component-annotation-default";
31 import type { Formatter } from "./types";
32
33 @injectable()
34 export class PlainFormatter implements Formatter {
35 format(message: string): string {
36 return message;
37 }
38 }
39 {{/code}}
40
41 {{code language="typescript"}}
42 // src/loud-formatter.ts
43 import { injectable } from "@xwiki/platform-component-annotation-default";
44 import type { Formatter } from "./types";
45
46 @injectable()
47 export class LoudFormatter implements Formatter {
48 format(message: string): string {
49 return message.toUpperCase() + "!";
50 }
51 }
52 {{/code}}
53
54 {{code language="typescript"}}
55 // src/console-channel.ts
56 import { injectable } from "@xwiki/platform-component-annotation-default";
57 import type { Channel } from "./types";
58
59 @injectable()
60 export class ConsoleChannel implements Channel {
61 async send(message: string): Promise<void> {
62 console.log(`[console] ${message}`);
63 }
64 }
65 {{/code}}
66
67 {{code language="typescript"}}
68 // src/memory-channel.ts
69 import { injectable } from "@xwiki/platform-component-annotation-default";
70 import type { Channel } from "./types";
71
72 @injectable()
73 export class MemoryChannel implements Channel {
74 public readonly sent: string[] = [];
75
76 async send(message: string): Promise<void> {
77 this.sent.push(message);
78 }
79 }
80 {{/code}}
81 )))
82 1. Implement a component that injects others(((
83 {{code language="typescript"}}
84 // src/default-notifier.ts
85 import { inject, injectAll, injectable, named } from "@xwiki/platform-component-annotation-default";
86 import { CHANNEL_ROLE, FORMATTER_ROLE } from "./roles";
87 import type { Channel, Formatter, Notifier } from "./types";
88
89 @injectable()
90 export class DefaultNotifier implements Notifier {
91 constructor(@inject(FORMATTER_ROLE) @named("loud") private readonly formatter: Formatter,
92 @injectAll(CHANNEL_ROLE) private readonly channels: Channel[], ) {}
93
94 async notify(message: string): Promise<void> {
95 const formatted = this.formatter.format(message);
96 await Promise.all(this.channels.map((c) => c.send(formatted)));
97 }
98 }
99 {{/code}}
100 )))
101 1. Register all components(((
102 {{code language="typescript"}}
103 // src/register.ts
104 import { manager } from "@xwiki/platform-component-manager-default";
105 import { CHANNEL_ROLE, FORMATTER_ROLE, NOTIFIER_ROLE } from "./roles";
106
107 manager
108 .registerComponent(
109 FORMATTER_ROLE,
110 () => import("./plain-formatter").then((m) => m.PlainFormatter),
111 { name: "plain" }
112 )
113 .registerComponent(
114 FORMATTER_ROLE,
115 () => import("./loud-formatter").then((m) => m.LoudFormatter),
116 { name: "loud" }
117 )
118 .registerComponent(
119 CHANNEL_ROLE,
120 () => import("./console-channel").then((m) => m.ConsoleChannel),
121 { name: "console" }
122 )
123 .registerComponent(
124 CHANNEL_ROLE,
125 () => import("./memory-channel").then((m) => m.MemoryChannel),
126 { name: "memory" }
127 )
128 .registerComponent(
129 NOTIFIER_ROLE,
130 () => import("./default-notifier").then((m) => m.DefaultNotifier)
131 );
132 {{/code}}
133 )))
134 1. Initialize and resolve(((
135 {{code language="typescript"}}
136 // src/bootstrap.ts
137 import "./register";
138 // side-effect import: triggers registrations
139 import { _init, resolverPromise } from "@xwiki/platform-component-manager-default";
140 import { NOTIFIER_ROLE } from "./roles";
141 import type { Notifier } from "./types";
142 async function main() {
143 await _init();
144 const resolver = await resolverPromise;
145 const notifier = await resolver.getAsync<Notifier>(NOTIFIER_ROLE);
146 await notifier.notify("hello world");
147 // → ConsoleChannel logs: "[console] HELLO WORLD!"
148 // → MemoryChannel appends "HELLO WORLD!" to its `sent` array.
149 }
150
151 main();
152 {{/code}}
153 )))

Get Connected