TypeScript
Completed
-
Section 1: Getting Started
-
Section 2: Basic Types
-
Section 3: Functions and Objects
-
Section 4: Advanced Types
-
Section 5: Object-Oriented TypeScript
-
Section 6: Working with Modules
-
Section 7: Tooling and Practice
-
Section 8: Type-Level Programming
-
Section 9: TypeScript with Backends
-
Section 10: Testing Typed Code
-
Section 11: Data Validation with Types
-
Section 12: Practical Projects
-
Section 13: Compiler Internals
-
Section 14: Configuration Deep Dive
-
Section 15: Enums, Symbols, and Special Types
-
Section 16: Working with Async Code
-
Section 17: TypeScript and the DOM
-
Section 18: Advanced Generics Practice
-
Section 19: Working with Third-Party Types
-
Section 20: Monorepo and Large-Scale Practices
-
Section 21: Common Pitfalls and Best Practices
-
Section 22: Interview Practice
-
Section 23: Handbook: Narrowing In Depth
-
Section 24: Handbook: Object Types In Depth
-
Section 25: Handbook: Classes In Depth
-
Section 26: Handbook: Modules In Depth
-
Section 27: Handbook: Declaration Files In Depth
-
Section 28: JSX and Namespaces
-
Section 29: Compiler Configuration Reference
-
Section 30: More Practice Exercises
-
Section 31: Handbook: Everyday Types Deep Dive
-
Section 32: Utility Types Full Reference
-
Section 33: Decorators Reference
-
Section 34: Mixins and Advanced OOP Patterns
-
Section 35: Iterators and Generators Typing
-
Section 36: More Type-Level Programming Practice
-
Section 37: TypeScript Ecosystem Tools
-
Section 38: TypeScript with Testing Frameworks
-
Section 39: TypeScript for Library Authors
-
Section 40: More Interview Practice
-
Section 41: Handbook: Functions In Depth
-
Section 42: Handbook: Type Manipulation Deep Dive
-
Section 43: More Real-World Patterns
-
Section 44: TypeScript Release Notes Highlights
-
Section 45: Final Practice and Review
239: Typing a Plugin Registry
Imagine you're building a high-end modular synthesizer. The synth has a main chassis with several empty slots. You don't know exactly which modules a musician will buy—maybe a filter, an oscillator, or a sequencer—but you do know that any module plugged into the "Filter" slot must have a specific set of knobs and voltage inputs, otherwise, it'll fry the circuit.
The chassis is your Plugin Registry. It doesn't care about the specific brand or model of the module; it just cares that if something claims to be a "Filter," it adheres to the "Filter" interface. If you try to jam a sequencer module into a filter slot, the physical shape (the type system) should prevent it from fitting.
In TypeScript, we achieve this by creating a registry that maps specific keys (the slots) to specific interfaces (the module requirements).
Defining the Plugin Blueprint
First, we need to define what our plugins actually look like. Let's say we're building a payment processing system. We might have different providers like Stripe or PayPal, but each must implement a common set of methods.
interface PaymentProvider {
processPayment(amount: number): Promise<boolean>;
}
interface RefundProvider {
refundPayment(transactionId: string): Promise<boolean>;
}
// This is our "Slot Map"
interface PluginSchema {
payments: PaymentProvider;
refunds: RefundProvider;
}
I like to keep this schema separate. It acts as the single source of truth for what the system supports. If you want to add a "TaxCalculator" plugin later, you just add one line to this interface.
Building the Type-Safe Registry
Now, here is where most people trip up. If you just use a Map<string, any>, you've thrown away all the benefits of TypeScript. We want the registry to know that if we ask for the 'payments' plugin, we are getting a PaymentProvider, not a RefundProvider.
We can do this using generics and indexed access types:
class PluginRegistry<T extends Record<string, any>> {
private plugins = new Map<string, any>();
// We use K extends keyof T to ensure only valid slots are used
register<K extends keyof T>(id: K, plugin: T[K]): void {
this.plugins.set(id as string, plugin);
console.log(`Registered plugin for slot: ${id as string}`);
}
get<K extends keyof T>(id: K): T[K] {
const plugin = this.plugins.get(id as string);
if (!plugin) {
throw new Error(`Plugin ${id as string} not found!`);
}
return plugin;
}
}
Notice how T[K] works here. It's telling TypeScript: "Look at the schema T and find the type associated with the key K." It's the secret sauce that gives us perfect autocomplete and compile-time errors.
Putting it into Practice
When we instantiate the registry, we pass in our PluginSchema. From that point on, the registry is locked into those specific types.
const registry = new PluginRegistry<PluginSchema>();
const stripePlugin: PaymentProvider = {
processPayment: async (amt) => {
console.log(`Charging $${amt} via Stripe...`);
return true;
}
};
// This works perfectly
registry.register('payments', stripePlugin);
// This would cause a TypeScript error because 'refunds' expects a RefundProvider
// registry.register('refunds', stripePlugin);
const paymentSvc = registry.get('payments');
paymentSvc.processPayment(100); // TypeScript knows this is a PaymentProvider
I've found that this pattern is incredibly useful for large-scale applications where you want to decouple the core logic from the implementation details. You can ship the PluginRegistry and the PluginSchema in a core package, and let other teams write the actual plugin implementations in separate libraries.
📋 Practical Task
Exercise: Implementing a Custom Data Exporter Registry
You are building a data reporting tool. The tool needs to be able to export data in different formats (CSV, JSON, and PDF). Each exporter has a different configuration requirement: CSV needs a delimiter, JSON needs a prettyPrint boolean, and PDF needs a pageSize string.
Your task:
- Define three interfaces:
CSVExporter,JSONExporter, andPDFExporter. Each should have anexport(data: any): voidmethod and their specific configuration property. - Create a
ExporterSchemainterface that maps the keys'csv','json', and'pdf'to these interfaces. - Implement a
Registryclass (similar to the one in the lesson) that uses generics to ensure that only the correct exporter can be registered to the correct key. - Instantiate the registry and register a valid
JSONExporter. - Attempt to register a
CSVExporterto the'json'key and verify that TypeScript throws a type error.
There are no comments for now.