0
# Plugin Orchestration
1
2
Central orchestration system providing unified access to containers, clocks, and debugging capabilities. The Ctx class serves as the main interface for plugin development, state coordination, and timer management within the Milkdown ecosystem.
3
4
## Capabilities
5
6
### Ctx Class
7
8
Main orchestration class that coordinates access to containers, clocks, and provides debugging capabilities. Essential for plugin development and managing the complete context lifecycle.
9
10
```typescript { .api }
11
/**
12
* Ctx provides unified access to containers, clocks, and debugging tools
13
*/
14
class Ctx {
15
/** Metadata associated with this context (readonly) */
16
readonly meta: Meta | undefined;
17
/** Inspector instance for debugging and telemetry (readonly) */
18
readonly inspector: Inspector | undefined;
19
20
/** Create a ctx object with container, clock, and optional metadata */
21
constructor(container: Container, clock: Clock, meta?: Meta);
22
23
/** Create a new ctx with additional metadata, returns same instance if empty meta */
24
readonly produce: (meta?: Meta) => Ctx;
25
26
/** Add a slice into the ctx with optional initial value */
27
readonly inject: <T>(sliceType: SliceType<T>, value?: T) => Ctx;
28
/** Remove a slice from the ctx by type or name */
29
readonly remove: <T, N extends string = string>(sliceType: SliceType<T, N> | N) => Ctx;
30
/** Check if the ctx has a slice by type or name */
31
readonly isInjected: <T, N extends string = string>(sliceType: SliceType<T, N> | N) => boolean;
32
33
/** Get a slice instance from the ctx */
34
readonly use: <T, N extends string = string>(sliceType: SliceType<T, N> | N) => Slice<T, N>;
35
/** Get a slice value from the ctx */
36
readonly get: <T, N extends string>(sliceType: SliceType<T, N> | N) => T;
37
/** Set a slice value in the ctx */
38
readonly set: <T, N extends string>(sliceType: SliceType<T, N> | N, value: T) => void;
39
/** Update a slice value using a callback function */
40
readonly update: <T, N extends string>(sliceType: SliceType<T, N> | N, updater: (prev: T) => T) => void;
41
42
/** Add a timer into the ctx */
43
readonly record: (timerType: TimerType) => Ctx;
44
/** Remove a timer from the ctx */
45
readonly clearTimer: (timerType: TimerType) => Ctx;
46
/** Check if the ctx has a timer */
47
readonly isRecorded: (timerType: TimerType) => boolean;
48
/** Get a timer instance from the ctx */
49
readonly timer: (timer: TimerType) => Timer;
50
/** Resolve a timer */
51
readonly done: (timer: TimerType) => void;
52
/** Start a timer and return its promise */
53
readonly wait: (timer: TimerType) => Promise<void>;
54
/** Wait for an array of timers stored in a slice */
55
readonly waitTimers: (slice: SliceType<TimerType[]>) => Promise<void>;
56
}
57
```
58
59
**Usage Examples:**
60
61
```typescript
62
import { Container, Clock, Ctx, createSlice, createTimer } from "@milkdown/ctx";
63
64
// Setup context
65
const container = new Container();
66
const clock = new Clock();
67
const ctx = new Ctx(container, clock);
68
69
// Slice management
70
const counterSlice = createSlice(0, "counter");
71
ctx.inject(counterSlice, 10);
72
73
console.log(ctx.get(counterSlice)); // 10
74
ctx.update(counterSlice, (prev) => prev + 5);
75
console.log(ctx.get(counterSlice)); // 15
76
77
// Timer management
78
const loadTimer = createTimer("load-data", 5000);
79
ctx.record(loadTimer);
80
81
// Simulate async operation
82
setTimeout(() => ctx.done(loadTimer), 2000);
83
84
// Wait for timer
85
ctx.wait(loadTimer).then(() => {
86
console.log("Data loaded!");
87
});
88
```
89
90
### Advanced Context Operations
91
92
**Metadata and Inspector Integration:**
93
94
```typescript
95
import { Ctx, Container, Clock, type Meta } from "@milkdown/ctx";
96
97
const meta: Meta = {
98
displayName: "Data Plugin",
99
description: "Handles data processing",
100
package: "@example/data-plugin",
101
group: "Core"
102
};
103
104
const ctx = new Ctx(new Container(), new Clock(), meta);
105
106
// Create contexts with different metadata
107
const apiCtx = ctx.produce({
108
displayName: "API Handler",
109
package: "@example/api",
110
additional: { endpoint: "/api/v1" }
111
});
112
113
// Access inspector for debugging
114
if (ctx.inspector) {
115
const telemetry = ctx.inspector.read();
116
console.log("Plugin metadata:", telemetry.metadata);
117
console.log("Injected slices:", telemetry.injectedSlices);
118
}
119
```
120
121
**Complex Timer Coordination:**
122
123
```typescript
124
import { Ctx, Container, Clock, createSlice, createTimer, TimerType } from "@milkdown/ctx";
125
126
// Setup context and timers
127
const container = new Container();
128
const clock = new Clock();
129
const ctx = new Ctx(container, clock);
130
const initTimer = createTimer("init", 3000);
131
const loadTimer = createTimer("load", 5000);
132
const renderTimer = createTimer("render", 2000);
133
134
// Create slice to hold timer array
135
const pipelineTimers = createSlice<TimerType[]>([], "pipeline");
136
ctx.inject(pipelineTimers, [initTimer, loadTimer, renderTimer]);
137
138
// Record all timers
139
[initTimer, loadTimer, renderTimer].forEach(timer => ctx.record(timer));
140
141
// Simulate pipeline completion
142
setTimeout(() => ctx.done(initTimer), 1000);
143
setTimeout(() => ctx.done(loadTimer), 2000);
144
setTimeout(() => ctx.done(renderTimer), 3000);
145
146
// Wait for entire pipeline
147
ctx.waitTimers(pipelineTimers).then(() => {
148
console.log("Pipeline completed!");
149
});
150
```
151
152
### MilkdownPlugin Type
153
154
Type definition for Milkdown plugins with optional metadata and lifecycle support.
155
156
```typescript { .api }
157
/**
158
* Plugin function type with optional metadata and lifecycle management
159
*/
160
type MilkdownPlugin = { meta?: Meta } & ((ctx: Ctx) => CtxRunner);
161
162
type CtxRunner = () => RunnerReturnType;
163
type RunnerReturnType = void | Promise<void> | Cleanup | Promise<Cleanup>;
164
type Cleanup = () => void | Promise<void>;
165
```
166
167
**Usage Examples:**
168
169
```typescript
170
import { type MilkdownPlugin, type Meta } from "@milkdown/ctx";
171
172
// Simple plugin
173
const simplePlugin: MilkdownPlugin = (ctx) => {
174
// Setup phase
175
const dataSlice = createSlice([], "plugin-data");
176
ctx.inject(dataSlice);
177
178
return () => {
179
// Run phase
180
console.log("Plugin running");
181
};
182
};
183
184
// Full lifecycle plugin with metadata
185
const fullPlugin: MilkdownPlugin = Object.assign(
186
(ctx) => {
187
// Setup phase
188
const configSlice = createSlice({ enabled: true }, "config");
189
ctx.inject(configSlice);
190
191
return async () => {
192
// Run phase
193
console.log("Async plugin running");
194
195
return async () => {
196
// Cleanup phase
197
console.log("Plugin cleaning up");
198
ctx.remove(configSlice);
199
};
200
};
201
},
202
{
203
meta: {
204
displayName: "Full Lifecycle Plugin",
205
description: "Demonstrates complete plugin lifecycle",
206
package: "@example/full-plugin",
207
group: "Examples"
208
} as Meta
209
}
210
);
211
212
// Plugin with error handling
213
const robustPlugin: MilkdownPlugin = (ctx) => {
214
const errorSlice = createSlice<Error | null>(null, "errors");
215
ctx.inject(errorSlice);
216
217
return () => {
218
try {
219
// Plugin logic
220
console.log("Plugin executing");
221
} catch (error) {
222
ctx.set(errorSlice, error as Error);
223
console.error("Plugin error:", error);
224
}
225
226
return () => {
227
// Cleanup
228
if (ctx.isInjected(errorSlice)) {
229
ctx.remove(errorSlice);
230
}
231
};
232
};
233
};
234
```
235
236
## Integration Patterns
237
238
### Plugin Composition
239
240
```typescript
241
import { Ctx, Container, Clock, type MilkdownPlugin } from "@milkdown/ctx";
242
243
// Create shared context
244
const sharedCtx = new Ctx(new Container(), new Clock());
245
246
// Plugin that provides services
247
const servicePlugin: MilkdownPlugin = (ctx) => {
248
const apiService = createSlice({ baseUrl: "/api" }, "api-service");
249
ctx.inject(apiService);
250
251
return () => {
252
console.log("Service plugin initialized");
253
};
254
};
255
256
// Plugin that consumes services
257
const consumerPlugin: MilkdownPlugin = (ctx) => {
258
return () => {
259
if (ctx.isInjected("api-service")) {
260
const service = ctx.get("api-service");
261
console.log("Using API service:", service.baseUrl);
262
}
263
};
264
};
265
266
// Initialize plugins in order
267
const serviceRunner = servicePlugin(sharedCtx);
268
const consumerRunner = consumerPlugin(sharedCtx);
269
270
serviceRunner();
271
consumerRunner();
272
```
273
274
### State Synchronization
275
276
```typescript
277
// Plugin that manages shared state
278
const stateManagerPlugin: MilkdownPlugin = (ctx) => {
279
const appState = createSlice({
280
theme: "light",
281
language: "en"
282
}, "app-state");
283
284
ctx.inject(appState);
285
286
// Watch for state changes
287
const stateSlice = ctx.use(appState);
288
stateSlice.on((newState) => {
289
console.log("App state changed:", newState);
290
// Persist to localStorage, sync with server, etc.
291
});
292
293
return () => {
294
console.log("State manager ready");
295
};
296
};
297
```