0
# Token System
1
2
Runtime tokens that capture compile-time type information for type-safe object identity and dependency injection systems. Tokens provide a way to create unique identifiers that maintain type safety at runtime.
3
4
## Capabilities
5
6
### Token Class
7
8
A runtime object which captures compile-time type information, allowing type-safe identification and dependency injection.
9
10
```typescript { .api }
11
/**
12
* A runtime object which captures compile-time type information.
13
*
14
* Notes:
15
* A token captures the compile-time type of an interface or class in
16
* an object which can be used at runtime in a type-safe fashion.
17
*/
18
class Token<T> {
19
/**
20
* Construct a new token
21
* @param name - A human readable name for the token
22
*/
23
constructor(name: string);
24
25
/**
26
* The human readable name for the token.
27
*
28
* Notes:
29
* This can be useful for debugging and logging.
30
*/
31
readonly name: string;
32
}
33
```
34
35
**Usage Examples:**
36
37
```typescript
38
import { Token } from "@lumino/coreutils";
39
40
// Define interfaces for your types
41
interface User {
42
id: number;
43
name: string;
44
email: string;
45
}
46
47
interface Config {
48
apiUrl: string;
49
theme: string;
50
debug: boolean;
51
}
52
53
interface Logger {
54
log(message: string): void;
55
error(message: string): void;
56
}
57
58
// Create tokens for each type
59
const UserToken = new Token<User>("user");
60
const ConfigToken = new Token<Config>("config");
61
const LoggerToken = new Token<Logger>("logger");
62
63
// Tokens can be used for type-safe identification
64
console.log(UserToken.name); // "user"
65
console.log(ConfigToken.name); // "config"
66
console.log(LoggerToken.name); // "logger"
67
68
// Each token is unique, even with the same name
69
const AnotherUserToken = new Token<User>("user");
70
console.log(UserToken === AnotherUserToken); // false
71
```
72
73
### Common Use Cases
74
75
**Dependency Injection:**
76
77
```typescript
78
import { Token } from "@lumino/coreutils";
79
80
// Define service interfaces
81
interface DatabaseService {
82
query(sql: string): Promise<any[]>;
83
save(entity: any): Promise<void>;
84
}
85
86
interface EmailService {
87
sendEmail(to: string, subject: string, body: string): Promise<void>;
88
}
89
90
interface UserService {
91
getUser(id: number): Promise<User>;
92
createUser(userData: Partial<User>): Promise<User>;
93
}
94
95
// Create tokens for services
96
const DatabaseToken = new Token<DatabaseService>("database");
97
const EmailToken = new Token<EmailService>("email");
98
const UserServiceToken = new Token<UserService>("user-service");
99
100
// Simple dependency injection container
101
class Container {
102
private services = new Map<Token<any>, any>();
103
104
register<T>(token: Token<T>, implementation: T): void {
105
this.services.set(token, implementation);
106
}
107
108
resolve<T>(token: Token<T>): T {
109
const service = this.services.get(token);
110
if (!service) {
111
throw new Error(`Service not found for token: ${token.name}`);
112
}
113
return service;
114
}
115
}
116
117
// Usage
118
const container = new Container();
119
120
// Register services
121
container.register(DatabaseToken, new SqliteDatabase());
122
container.register(EmailToken, new SmtpEmailService());
123
container.register(UserServiceToken, new UserServiceImpl(
124
container.resolve(DatabaseToken),
125
container.resolve(EmailToken)
126
));
127
128
// Resolve services with type safety
129
const userService = container.resolve(UserServiceToken); // Type is UserService
130
const user = await userService.getUser(123);
131
```
132
133
**Plugin System:**
134
135
```typescript
136
import { Token } from "@lumino/coreutils";
137
138
// Define plugin interfaces
139
interface Plugin {
140
name: string;
141
version: string;
142
activate(app: Application): void;
143
deactivate(): void;
144
}
145
146
interface ThemePlugin extends Plugin {
147
getThemes(): Theme[];
148
applyTheme(themeName: string): void;
149
}
150
151
interface EditorPlugin extends Plugin {
152
createEditor(element: HTMLElement): Editor;
153
}
154
155
// Create tokens for different plugin types
156
const ThemePluginToken = new Token<ThemePlugin>("theme-plugin");
157
const EditorPluginToken = new Token<EditorPlugin>("editor-plugin");
158
159
// Plugin registry
160
class PluginRegistry {
161
private plugins = new Map<Token<any>, Plugin[]>();
162
163
registerPlugin<T extends Plugin>(token: Token<T>, plugin: T): void {
164
if (!this.plugins.has(token)) {
165
this.plugins.set(token, []);
166
}
167
this.plugins.get(token)!.push(plugin);
168
}
169
170
getPlugins<T extends Plugin>(token: Token<T>): T[] {
171
return (this.plugins.get(token) || []) as T[];
172
}
173
174
getPlugin<T extends Plugin>(token: Token<T>, name: string): T | undefined {
175
const plugins = this.getPlugins(token);
176
return plugins.find(p => p.name === name) as T | undefined;
177
}
178
}
179
180
// Usage
181
const registry = new PluginRegistry();
182
183
// Register plugins
184
registry.registerPlugin(ThemePluginToken, new DarkThemePlugin());
185
registry.registerPlugin(ThemePluginToken, new LightThemePlugin());
186
registry.registerPlugin(EditorPluginToken, new MonacoEditorPlugin());
187
188
// Get plugins with type safety
189
const themePlugins = registry.getPlugins(ThemePluginToken); // Type is ThemePlugin[]
190
const darkTheme = registry.getPlugin(ThemePluginToken, "dark-theme"); // Type is ThemePlugin | undefined
191
```
192
193
**Event System with Type Safety:**
194
195
```typescript
196
import { Token } from "@lumino/coreutils";
197
198
// Define event payload types
199
interface UserLoginEvent {
200
userId: number;
201
timestamp: Date;
202
source: string;
203
}
204
205
interface FileUploadEvent {
206
filename: string;
207
size: number;
208
mimeType: string;
209
}
210
211
interface SystemErrorEvent {
212
error: Error;
213
context: string;
214
severity: "low" | "medium" | "high" | "critical";
215
}
216
217
// Create tokens for each event type
218
const UserLoginToken = new Token<UserLoginEvent>("user-login");
219
const FileUploadToken = new Token<FileUploadEvent>("file-upload");
220
const SystemErrorToken = new Token<SystemErrorEvent>("system-error");
221
222
// Type-safe event emitter
223
class TypedEventEmitter {
224
private listeners = new Map<Token<any>, Function[]>();
225
226
on<T>(token: Token<T>, listener: (event: T) => void): void {
227
if (!this.listeners.has(token)) {
228
this.listeners.set(token, []);
229
}
230
this.listeners.get(token)!.push(listener);
231
}
232
233
emit<T>(token: Token<T>, event: T): void {
234
const eventListeners = this.listeners.get(token) || [];
235
eventListeners.forEach(listener => listener(event));
236
}
237
238
off<T>(token: Token<T>, listener: (event: T) => void): void {
239
const eventListeners = this.listeners.get(token) || [];
240
const index = eventListeners.indexOf(listener);
241
if (index !== -1) {
242
eventListeners.splice(index, 1);
243
}
244
}
245
}
246
247
// Usage
248
const eventEmitter = new TypedEventEmitter();
249
250
// Register type-safe listeners
251
eventEmitter.on(UserLoginToken, (event) => {
252
// event is typed as UserLoginEvent
253
console.log(`User ${event.userId} logged in from ${event.source}`);
254
});
255
256
eventEmitter.on(FileUploadToken, (event) => {
257
// event is typed as FileUploadEvent
258
console.log(`File uploaded: ${event.filename} (${event.size} bytes)`);
259
});
260
261
eventEmitter.on(SystemErrorToken, (event) => {
262
// event is typed as SystemErrorEvent
263
if (event.severity === "critical") {
264
console.error(`Critical error in ${event.context}:`, event.error);
265
}
266
});
267
268
// Emit events with type safety
269
eventEmitter.emit(UserLoginToken, {
270
userId: 123,
271
timestamp: new Date(),
272
source: "web"
273
});
274
275
eventEmitter.emit(FileUploadToken, {
276
filename: "document.pdf",
277
size: 1024000,
278
mimeType: "application/pdf"
279
});
280
```
281
282
**Configuration Management:**
283
284
```typescript
285
import { Token } from "@lumino/coreutils";
286
287
// Define configuration interfaces
288
interface DatabaseConfig {
289
host: string;
290
port: number;
291
database: string;
292
username: string;
293
password: string;
294
}
295
296
interface ApiConfig {
297
baseUrl: string;
298
timeout: number;
299
apiKey: string;
300
}
301
302
interface UiConfig {
303
theme: "light" | "dark";
304
language: string;
305
animations: boolean;
306
}
307
308
// Create configuration tokens
309
const DatabaseConfigToken = new Token<DatabaseConfig>("database-config");
310
const ApiConfigToken = new Token<ApiConfig>("api-config");
311
const UiConfigToken = new Token<UiConfig>("ui-config");
312
313
// Configuration manager
314
class ConfigManager {
315
private configs = new Map<Token<any>, any>();
316
317
set<T>(token: Token<T>, config: T): void {
318
this.configs.set(token, config);
319
}
320
321
get<T>(token: Token<T>): T {
322
const config = this.configs.get(token);
323
if (!config) {
324
throw new Error(`Configuration not found for: ${token.name}`);
325
}
326
return config;
327
}
328
329
has<T>(token: Token<T>): boolean {
330
return this.configs.has(token);
331
}
332
}
333
334
// Usage
335
const configManager = new ConfigManager();
336
337
// Set configurations
338
configManager.set(DatabaseConfigToken, {
339
host: "localhost",
340
port: 5432,
341
database: "myapp",
342
username: "user",
343
password: "password"
344
});
345
346
configManager.set(ApiConfigToken, {
347
baseUrl: "https://api.example.com",
348
timeout: 5000,
349
apiKey: "secret-key"
350
});
351
352
// Get configurations with type safety
353
const dbConfig = configManager.get(DatabaseConfigToken); // Type is DatabaseConfig
354
const apiConfig = configManager.get(ApiConfigToken); // Type is ApiConfig
355
356
console.log(`Connecting to database at ${dbConfig.host}:${dbConfig.port}`);
357
console.log(`API base URL: ${apiConfig.baseUrl}`);
358
```