supabase
Handles the full Supabase workflow from schema changes to deployment, with built-in security guardrails that catch common traps like RLS...
obsidian-rate-limits
Handle Obsidian file system operations and throttling patterns. Use when processing many files, handling bulk operations, or preventing performance issues from excessive operations. Trigger with phrases like "obsidian rate limit", "obsidian bulk operations", "obsidian file throttling", "obsidian ...
Full skill instructions
Manage file system operations and implement throttling to prevent performance issues in Obsidian plugins.
| Operation | Recommended Limit | Risk if Exceeded |
|---|---|---|
| File reads | 100/second | UI freeze |
| File writes | 10/second | Data corruption risk |
| Metadata cache reads | 1000/second | Memory pressure |
| DOM updates | 60/second | Visual lag |
| Event emissions | 100/second | Event queue backup |
// src/utils/async-queue.ts
export class AsyncQueue {
private queue: Array<() => Promise<void>> = [];
private processing = false;
private concurrency: number;
private activeCount = 0;
constructor(concurrency: number = 1) {
this.concurrency = concurrency;
}
async add<T>(task: () => Promise<T>): Promise<T> {
return new Promise((resolve, reject) => {
this.queue.push(async () => {
try {
const result = await task();
resolve(result);
} catch (error) {
reject(error);
}
});
this.process();
});
}
private async process(): Promise<void> {
if (this.activeCount >= this.concurrency) return;
const task = this.queue.shift();
if (!task) return;
this.activeCount++;
try {
await task();
} finally {
this.activeCount--;
this.process();
}
}
get pending(): number {
return this.queue.length;
}
get active(): number {
return this.activeCount;
}
}
// src/utils/rate-limiter.ts
export class RateLimiter {
private timestamps: number[] = [];
private limit: number;
private window: number; // milliseconds
constructor(limit: number, windowMs: number) {
this.limit = limit;
this.window = windowMs;
}
async acquire(): Promise<void> {
const now = Date.now();
// Remove timestamps outside window
this.timestamps = this.timestamps.filter(t => now - t < this.window);
if (this.timestamps.length >= this.limit) {
// Calculate wait time
const oldestTimestamp = this.timestamps[0];
const waitTime = this.window - (now - oldestTimestamp);
await this.sleep(waitTime);
return this.acquire();
}
this.timestamps.push(now);
}
private sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// Usage:
const writeLimiter = new RateLimiter(10, 1000); // 10 per second
async function safeWriteFile(file: TFile, content: string) {
await writeLimiter.acquire();
await this.app.vault.modify(file, content);
}
// src/utils/batch-processor.ts
export interface BatchOptions {
batchSize: number;
delayBetweenBatches: number;
onProgress?: (processed: number, total: number) => void;
}
export async function processBatches<T, R>(
items: T[],
processor: (item: T) => Promise<R>,
options: BatchOptions
): Promise<R[]> {
const results: R[] = [];
const total = items.length;
for (let i = 0; i < items.length; i += options.batchSize) {
const batch = items.slice(i, i + options.batchSize);
// Process batch in parallel
const batchResults = await Promise.all(
batch.map(item => processor(item))
);
results.push(...batchResults);
// Report progress
options.onProgress?.(Math.min(i + options.batchSize, total), total);
// Delay between batches (except for last batch)
if (i + options.batchSize < items.length) {
await sleep(options.delayBetweenBatches);
}
}
return results;
}
function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Usage:
const files = this.app.vault.getMarkdownFiles();
const results = await processBatches(
files,
async (file) => {
const content = await this.app.vault.read(file);
return { path: file.path, length: content.length };
},
{
batchSize: 50,
delayBetweenBatches: 100, // 100ms between batches
onProgress: (processed, total) => {
console.log(`Processed ${processed}/${total}`);
},
}
);
// src/utils/debounce.ts
export function debounce<T extends (...args: any[]) => any>(
func: T,
wait: number,
options: { leading?: boolean; trailing?: boolean; maxWait?: number } = {}
): (...args: Parameters<T>) => void {
let timeout: NodeJS.Timeout | null = null;
let lastCallTime: number | null = null;
let lastInvokeTime = 0;
let result: ReturnType<T>;
const { leading = false, trailing = true, maxWait } = options;
function invokeFunc(time: number, args: Parameters<T>) {
lastInvokeTime = time;
result = func(...args);
return result;
}
return function (...args: Parameters<T>) {
const time = Date.now();
const isInvoking = shouldInvoke(time);
lastCallTime = time;
if (isInvoking) {
if (!timeout && leading) {
return invokeFunc(time, args);
}
}
if (!timeout) {
timeout = setTimeout(() => {
timeout = null;
if (trailing && lastCallTime) {
invokeFunc(Date.now(), args);
}
}, wait);
}
// Handle maxWait
if (maxWait !== undefined) {
const timeSinceLastInvoke = time - lastInvokeTime;
if (timeSinceLastInvoke >= maxWait) {
invokeFunc(time, args);
}
}
};
function shouldInvoke(time: number): boolean {
const timeSinceLastCall = lastCallTime ? time - lastCallTime : 0;
return !lastCallTime || timeSinceLastCall >= wait;
}
}
// Usage for search input:
const debouncedSearch = debounce(
async (query: string) => {
const results = await performSearch(query);
updateUI(results);
},
300,
{ leading: false, trailing: true }
);
// Typing triggers search only after 300ms pause
inputEl.addEventListener('input', (e) => {
debouncedSearch(e.target.value);
});
// src/utils/throttle.ts
export function throttle<T extends (...args: any[]) => any>(
func: T,
limit: number
): (...args: Parameters<T>) => void {
let inThrottle = false;
let lastArgs: Parameters<T> | null = null;
return function (...args: Parameters<T>) {
if (!inThrottle) {
func(...args);
inThrottle = true;
setTimeout(() => {
inThrottle = false;
if (lastArgs) {
func(...lastArgs);
lastArgs = null;
}
}, limit);
} else {
lastArgs = args;
}
};
}
// Usage for scroll handling:
const throttledOnScroll = throttle(() => {
// Update something based on scroll position
console.log('Scroll position:', window.scrollY);
}, 100);
window.addEventListener('scroll', throttledOnScroll);
| Issue | Cause | Solution |
|---|---|---|
| UI freezes | Too many sync operations | Use async with batching |
| Data loss | Write conflicts | Use async queue |
| Memory pressure | Too many cached reads | Use generators |
| Event storms | No debouncing | Debounce user input |
| Missed updates | Over-throttling | Reduce throttle time |
async function processAllFiles(app: App, plugin: Plugin) {
const files = app.vault.getMarkdownFiles();
const progressModal = new ProgressModal(app);
progressModal.open();
try {
await processBatches(
files,
async (file) => {
// Process file
return processFile(file);
},
{
batchSize: 20,
delayBetweenBatches: 50,
onProgress: (processed, total) => {
const percent = Math.round((processed / total) * 100);
progressModal.setProgress(percent, `Processing ${processed}/${total} files`);
},
}
);
new Notice('Processing complete!');
} finally {
progressModal.close();
}
}
async function* iterateFilesWithPause(
files: TFile[],
pauseEvery: number = 100,
pauseMs: number = 10
): AsyncGenerator<TFile> {
for (let i = 0; i < files.length; i++) {
yield files[i];
// Pause periodically to allow UI updates
if (i > 0 && i % pauseEvery === 0) {
await new Promise(r => setTimeout(r, pauseMs));
}
}
}
// Usage:
for await (const file of iterateFilesWithPause(files)) {
await processFile(file);
}
For security practices, see obsidian-security-basics.
Handles the full Supabase workflow from schema changes to deployment, with built-in security guardrails that catch common traps like RLS...
Comprehensive guides and best practices for Neon Serverless Postgres, covering setup, connection methods, authentication, and platform APIs.
Guide for setting up and using Firebase Authentication. Use this skill when the user's app requires user sign-in, user management, or secure data access using auth rules.
A skill to evaluate how secure Firestore security rules are. Use this when Firestore security rules are updated to ensure that the generated rules are extremely secure and robust.
Official skill for integrating Firebase AI Logic (Gemini API) into web applications. Covers setup, multimodal inference, structured output, and security.
Complete Better Auth server and client setup with database adapters, session management, plugins, and security configuration.
Deploy and manage projects on Vercel using token-based authentication. Use when working with Vercel CLI using access tokens rather than interactive login — e.g. "deploy to vercel", "set up vercel", "add environment variables to vercel".
Complete Cloudflare platform integration with decision trees for compute, storage, AI, networking, security, and infrastructure-as-code.
Run Azure compliance and security audits with azqr plus Key Vault expiration checks. Covers best-practice assessment, resource review, policy/compliance validation, and security posture checks. WHEN: compliance scan, security audit, BEFORE running azqr (compliance cli tool), Azure best practices, Key Vault expiration check, expired certificates, expiring secrets, orphaned resources, compliance assessment.
Send, read, and manage Gmail messages, drafts, labels, and account settings.
Comprehensive website auditing across 230+ rules in 21 categories including SEO, performance, security, and accessibility.
Shared authentication, CLI syntax, and output formatting patterns for gws Google Workspace commands.
Discover your celebrity doppelgänger with StarByFace!
GeoSpy: Pricing, Features, FAQs, and Alternatives for AI Teams
Detect AI-generated voices to protect against audio fraud.
Ensure Your Content's Originality with AI Plagiarism Checker
Upscale images by 400% without quality loss
Protect your social media from copyright disputes
Accurately Detect AI-Generated Content with TheChecker.AI
Chrome extension that detects and flags AI-generated content