Core context management module for Milkdown editor providing dependency injection and state management
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Debugging and telemetry system for monitoring ctx operations, slice usage, and timer execution. Provides comprehensive runtime analysis capabilities for development and production environments through the Inspector class and structured telemetry data.
Runtime debugging and telemetry collector that monitors all ctx operations including slice injection/consumption and timer execution with duration tracking.
/**
* Inspector provides runtime debugging and telemetry collection for ctx operations
*/
class Inspector {
/** Create an inspector with container, clock, and metadata references */
constructor(container: Container, clock: Clock, meta: Meta);
/** Read current telemetry data as a structured object */
read(): Telemetry;
}Usage Examples:
import {
Container,
Clock,
Ctx,
Inspector,
createSlice,
createTimer,
type Meta
} from "@milkdown/ctx";
// Setup context with metadata for inspection
const meta: Meta = {
displayName: "Data Processing Plugin",
description: "Handles data transformation and caching",
package: "@example/data-plugin",
group: "Core",
additional: { version: "1.2.0", author: "team@example.com" }
};
const container = new Container();
const clock = new Clock();
const ctx = new Ctx(container, clock, meta);
// Perform some operations
const dataSlice = createSlice([], "processed-data");
const cacheSlice = createSlice({}, "cache");
const loadTimer = createTimer("load-data", 3000);
ctx.inject(dataSlice, [1, 2, 3]);
ctx.inject(cacheSlice, { key1: "value1" });
ctx.record(loadTimer);
// Access inspector
if (ctx.inspector) {
const telemetry = ctx.inspector.read();
console.log("Plugin metadata:", telemetry.metadata);
console.log("Injected slices:", telemetry.injectedSlices);
console.log("Recorded timers:", telemetry.recordedTimers);
}Plugin metadata structure for debugging and identification purposes.
/**
* Meta interface defines plugin metadata for debugging and identification
*/
interface Meta {
/** Human-readable plugin name */
displayName: string;
/** Optional plugin description */
description?: string;
/** Package that the plugin belongs to */
package: string;
/** Optional plugin group (internal plugins use "System") */
group?: string;
/** Optional additional metadata as key-value pairs */
additional?: Record<string, any>;
}Usage Examples:
import { type Meta } from "@milkdown/ctx";
// Basic metadata
const basicMeta: Meta = {
displayName: "Simple Plugin",
package: "@example/simple"
};
// Complete metadata
const completeMeta: Meta = {
displayName: "Advanced Data Plugin",
description: "Provides advanced data processing capabilities",
package: "@example/advanced-data",
group: "Data Processing",
additional: {
version: "2.1.0",
author: "Jane Smith",
license: "MIT",
dependencies: ["lodash", "moment"],
experimental: true
}
};
// System plugin metadata
const systemMeta: Meta = {
displayName: "Core Timer System",
description: "Manages internal timing operations",
package: "@milkdown/core",
group: "System",
additional: {
internal: true,
priority: "high"
}
};Structured runtime telemetry data containing comprehensive information about plugin execution, slice usage, and timer performance.
/**
* Telemetry interface provides structured runtime data for analysis
*/
interface Telemetry {
/** Plugin metadata for identification */
metadata: Meta;
/** Array of injected slices with their current values */
injectedSlices: { name: string; value: unknown }[];
/** Array of consumed slices with their current values */
consumedSlices: { name: string; value: unknown }[];
/** Array of recorded timers with duration and status information */
recordedTimers: { name: string; duration: number; status: TimerStatus }[];
/** Array of waited timers with duration and status information */
waitTimers: { name: string; duration: number; status: TimerStatus }[];
}Usage Examples:
import { Ctx, Container, Clock, createSlice, createTimer, type Meta } from "@milkdown/ctx";
async function demonstrateTelemetry() {
const meta: Meta = {
displayName: "Telemetry Demo",
package: "@example/telemetry-demo"
};
const ctx = new Ctx(new Container(), new Clock(), meta);
// Setup slices and timers
const configSlice = createSlice({ theme: "dark" }, "config");
const dataSlice = createSlice<number[]>([], "data");
const initTimer = createTimer("initialization", 2000);
const loadTimer = createTimer("data-load", 5000);
// Inject slices
ctx.inject(configSlice, { theme: "light", lang: "en" });
ctx.inject(dataSlice, [1, 2, 3, 4, 5]);
// Record timers
ctx.record(initTimer);
ctx.record(loadTimer);
// Use slices (marks them as consumed)
const config = ctx.get(configSlice);
const data = ctx.get(dataSlice);
// Start timers
const initPromise = ctx.wait(initTimer);
const loadPromise = ctx.wait(loadTimer);
// Simulate completion
setTimeout(() => ctx.done(initTimer), 1000);
setTimeout(() => ctx.done(loadTimer), 2500);
// Wait for completion
await Promise.all([initPromise, loadPromise]);
// Read telemetry
if (ctx.inspector) {
const telemetry = ctx.inspector.read();
console.log("=== Telemetry Report ===");
console.log("Plugin:", telemetry.metadata.displayName);
console.log("Package:", telemetry.metadata.package);
console.log("\nInjected Slices:");
telemetry.injectedSlices.forEach(slice => {
console.log(`- ${slice.name}:`, slice.value);
});
console.log("\nConsumed Slices:");
telemetry.consumedSlices.forEach(slice => {
console.log(`- ${slice.name}:`, slice.value);
});
console.log("\nRecorded Timers:");
telemetry.recordedTimers.forEach(timer => {
console.log(`- ${timer.name}: ${timer.duration}ms (${timer.status})`);
});
console.log("\nWait Timers:");
telemetry.waitTimers.forEach(timer => {
console.log(`- ${timer.name}: ${timer.duration}ms (${timer.status})`);
});
}
}import { Ctx, Container, Clock, createTimer, type Meta } from "@milkdown/ctx";
class PerformanceMonitor {
private ctx: Ctx;
constructor(pluginName: string) {
const meta: Meta = {
displayName: `${pluginName} Performance Monitor`,
package: "@monitoring/performance",
group: "System",
additional: {
monitoringEnabled: true,
startTime: Date.now()
}
};
this.ctx = new Ctx(new Container(), new Clock(), meta);
}
async measureOperation(name: string, operation: () => Promise<void>) {
const timer = createTimer(`perf-${name}`, 30000);
this.ctx.record(timer);
const start = performance.now();
const promise = this.ctx.wait(timer);
try {
await operation();
this.ctx.done(timer);
await promise;
const duration = performance.now() - start;
console.log(`Operation ${name} completed in ${duration.toFixed(2)}ms`);
} catch (error) {
console.error(`Operation ${name} failed:`, error);
throw error;
}
}
getReport() {
if (!this.ctx.inspector) return null;
const telemetry = this.ctx.inspector.read();
return {
plugin: telemetry.metadata.displayName,
totalOperations: telemetry.waitTimers.length,
averageDuration: telemetry.waitTimers.reduce((sum, timer) =>
sum + timer.duration, 0) / telemetry.waitTimers.length,
failedOperations: telemetry.waitTimers.filter(timer =>
timer.status === 'rejected').length
};
}
}import { Ctx, Container, Clock, createSlice, type Meta } from "@milkdown/ctx";
class DebugContext {
private ctx: Ctx;
private debugEnabled: boolean;
constructor(debugEnabled = false) {
this.debugEnabled = debugEnabled;
const meta: Meta = {
displayName: "Debug Context",
package: "@debug/context",
group: "Development",
additional: {
debugEnabled,
environment: process.env.NODE_ENV || "development"
}
};
this.ctx = new Ctx(new Container(), new Clock(), meta);
}
injectSlice<T>(sliceType: SliceType<T>, value?: T) {
this.ctx.inject(sliceType, value);
if (this.debugEnabled) {
console.log(`🔧 Injected slice: ${sliceType.name}`, value);
this.logTelemetry();
}
return this;
}
useSlice<T>(sliceType: SliceType<T>) {
const result = this.ctx.use(sliceType);
if (this.debugEnabled) {
console.log(`📖 Using slice: ${sliceType.name}`, result.get());
}
return result;
}
private logTelemetry() {
if (!this.ctx.inspector || !this.debugEnabled) return;
const telemetry = this.ctx.inspector.read();
console.log("📊 Current telemetry:", {
injectedSlices: telemetry.injectedSlices.length,
consumedSlices: telemetry.consumedSlices.length,
activeTimers: telemetry.recordedTimers.filter(t => t.status === 'pending').length
});
}
getContext() {
return this.ctx;
}
}
// Usage
const debugCtx = new DebugContext(true);
const userSlice = createSlice({ name: "Anonymous" }, "user");
debugCtx
.injectSlice(userSlice, { name: "Alice" })
.useSlice(userSlice);import { Inspector, type Telemetry, type Meta } from "@milkdown/ctx";
class TelemetryCollector {
private telemetryData: Telemetry[] = [];
collectFromInspector(inspector: Inspector) {
const telemetry = inspector.read();
this.telemetryData.push({
...telemetry,
metadata: {
...telemetry.metadata,
additional: {
...telemetry.metadata.additional,
collectedAt: new Date().toISOString()
}
}
});
}
generateReport() {
const report = {
totalPlugins: this.telemetryData.length,
totalSlices: this.telemetryData.reduce((sum, t) =>
sum + t.injectedSlices.length, 0),
totalTimers: this.telemetryData.reduce((sum, t) =>
sum + t.recordedTimers.length, 0),
averageTimerDuration: this.calculateAverageTimerDuration(),
pluginsByGroup: this.groupPluginsByGroup(),
performanceMetrics: this.calculatePerformanceMetrics()
};
return report;
}
private calculateAverageTimerDuration(): number {
const allTimers = this.telemetryData.flatMap(t => t.waitTimers);
if (allTimers.length === 0) return 0;
return allTimers.reduce((sum, timer) => sum + timer.duration, 0) / allTimers.length;
}
private groupPluginsByGroup() {
const groups: Record<string, number> = {};
this.telemetryData.forEach(telemetry => {
const group = telemetry.metadata.group || "Ungrouped";
groups[group] = (groups[group] || 0) + 1;
});
return groups;
}
private calculatePerformanceMetrics() {
const allTimers = this.telemetryData.flatMap(t => t.waitTimers);
return {
totalTimers: allTimers.length,
successfulTimers: allTimers.filter(t => t.status === 'resolved').length,
failedTimers: allTimers.filter(t => t.status === 'rejected').length,
timeoutTimers: allTimers.filter(t => t.status === 'rejected').length,
averageDuration: this.calculateAverageTimerDuration(),
maxDuration: Math.max(...allTimers.map(t => t.duration), 0),
minDuration: Math.min(...allTimers.map(t => t.duration), 0)
};
}
exportToJson(): string {
return JSON.stringify({
generatedAt: new Date().toISOString(),
summary: this.generateReport(),
rawData: this.telemetryData
}, null, 2);
}
}The Inspector is automatically created when a Ctx is instantiated with metadata:
import { Ctx, Container, Clock, type Meta } from "@milkdown/ctx";
const meta: Meta = {
displayName: "Auto Inspector Plugin",
package: "@example/auto-inspector"
};
const ctx = new Ctx(new Container(), new Clock(), meta);
// Inspector is automatically available
console.log("Inspector available:", !!ctx.inspector); // true
// Without metadata, no inspector is created
const ctxWithoutMeta = new Ctx(new Container(), new Clock());
console.log("Inspector available:", !!ctxWithoutMeta.inspector); // false