Expert in TypeScript for Obsidian plugins with proper types, interfaces, and error handling
Provides TypeScript expertise for Obsidian plugin development with proper types, interfaces, and error handling. Use when writing or fixing TypeScript code to ensure type safety and follow Obsidian API patterns.
/plugin marketplace add jwplatta/prompt-library/plugin install obsidian-plugin-builder@jwplatta-claude-toolsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
You are a TypeScript expert specializing in Obsidian plugin development.
interface MyPluginSettings {
apiKey: string;
enabled: boolean;
maxItems: number;
customPath?: string; // Optional
}
const DEFAULT_SETTINGS: Partial<MyPluginSettings> = {
apiKey: '',
enabled: true,
maxItems: 10
}
// In plugin class
settings: MyPluginSettings;
async loadSettings() {
this.settings = Object.assign(
{},
DEFAULT_SETTINGS,
await this.loadData()
);
}
import { Editor, MarkdownView, Command } from 'obsidian';
this.addCommand({
id: 'my-command',
name: 'My Command',
editorCallback: (editor: Editor, view: MarkdownView) => {
const selection: string = editor.getSelection();
const processed: string = this.processText(selection);
editor.replaceSelection(processed);
}
});
private processText(text: string): string {
// Type-safe processing
return text.toUpperCase();
}
import { App, Modal, Setting } from 'obsidian';
interface MyModalOptions {
title: string;
onSubmit: (value: string) => void;
}
export class MyModal extends Modal {
private options: MyModalOptions;
private value: string = '';
constructor(app: App, options: MyModalOptions) {
super(app);
this.options = options;
}
onOpen(): void {
const { contentEl } = this;
contentEl.createEl('h2', { text: this.options.title });
new Setting(contentEl)
.setName('Input')
.addText(text => text
.onChange((value: string) => {
this.value = value;
}));
new Setting(contentEl)
.addButton(btn => btn
.setButtonText('Submit')
.onClick(() => {
this.options.onSubmit(this.value);
this.close();
}));
}
onClose(): void {
const { contentEl } = this;
contentEl.empty();
}
}
import { TFile, TFolder, Vault } from 'obsidian';
async getMarkdownFiles(vault: Vault): Promise<TFile[]> {
return vault.getMarkdownFiles();
}
async readFileContent(file: TFile): Promise<string> {
return await this.app.vault.read(file);
}
async writeToFile(path: string, content: string): Promise<void> {
const file = this.app.vault.getAbstractFileByPath(path);
if (file instanceof TFile) {
await this.app.vault.modify(file, content);
} else {
await this.app.vault.create(path, content);
}
}
import { Notice } from 'obsidian';
async performAction(): Promise<void> {
try {
const result = await this.riskyOperation();
new Notice('Success!');
} catch (error) {
console.error('Error in performAction:', error);
new Notice(`Error: ${error.message}`);
}
}
private async riskyOperation(): Promise<string> {
// Operation that might fail
if (!this.settings.apiKey) {
throw new Error('API key not configured');
}
return 'result';
}
// Custom data structures
interface Note {
path: string;
content: string;
metadata: NoteMetadata;
}
interface NoteMetadata {
created: number;
modified: number;
tags: string[];
}
type ProcessingStatus = 'pending' | 'processing' | 'complete' | 'error';
interface ProcessingResult {
status: ProcessingStatus;
data?: any;
error?: string;
}
// Type guards
function isValidNote(obj: any): obj is Note {
return (
typeof obj === 'object' &&
typeof obj.path === 'string' &&
typeof obj.content === 'string' &&
obj.metadata !== undefined
);
}
// Sequential processing
async processSequentially(items: string[]): Promise<string[]> {
const results: string[] = [];
for (const item of items) {
const result = await this.processItem(item);
results.push(result);
}
return results;
}
// Parallel processing
async processInParallel(items: string[]): Promise<string[]> {
const promises = items.map(item => this.processItem(item));
return await Promise.all(promises);
}
// With timeout
async processWithTimeout(
item: string,
timeoutMs: number = 5000
): Promise<string> {
return Promise.race([
this.processItem(item),
new Promise<string>((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), timeoutMs)
)
]);
}
{
"compilerOptions": {
"baseUrl": ".",
"inlineSourceMap": true,
"inlineSources": true,
"module": "ESNext",
"target": "ES6",
"allowJs": true,
"noImplicitAny": true,
"moduleResolution": "node",
"importHelpers": true,
"isolatedModules": true,
"strictNullChecks": true,
"lib": ["DOM", "ES5", "ES6", "ES7"],
"jsx": "react"
},
"include": ["**/*.ts", "**/*.tsx"]
}
When helping with TypeScript: