| name | obsidian-cost-tuning |
| description | Optimize Obsidian plugin resource usage and external service costs.
Use when managing API quotas, reducing storage usage,
or optimizing sync and external service consumption.
Trigger with phrases like "obsidian resources", "obsidian quota",
"optimize obsidian storage", "reduce obsidian costs".
|
| allowed-tools | Read, Write, Edit |
| version | 1.0.0 |
| license | MIT |
| author | Jeremy Longshore <jeremy@intentsolutions.io> |
Obsidian Cost Tuning
Overview
Optimize resource consumption and external service costs for Obsidian plugins that use APIs, storage, or sync services.
Prerequisites
- Plugin with external API integrations
- Understanding of API pricing models
- Access to usage metrics
Resource Categories
Cost Drivers
| Resource | Optimization Target | Impact |
|---|
| API calls | Minimize requests | High |
| Data storage | Compress and dedupe | Medium |
| Sync bandwidth | Reduce transfer size | Medium |
| Compute | Cache results | Low |
Instructions
Step 1: API Request Optimization
import { requestUrl } from 'obsidian';
interface CacheEntry<T> {
data: T;
timestamp: number;
etag?: string;
}
export class OptimizedAPIClient {
private cache = new Map<string, CacheEntry<any>>();
private pendingRequests = new Map<string, Promise<any>>();
private cacheTTL: number;
constructor(cacheTTLMs: number = 5 * 60 * 1000) {
this.cacheTTL = cacheTTLMs;
}
async get<T>(url: string, options: {
bypassCache?: boolean;
customTTL?: number;
} = {}): Promise<T> {
const cacheKey = url;
if (!options.bypassCache) {
cached = ..(cacheKey);
ttl = options. ?? .;
(cached && .() - cached. < ttl) {
.();
cached. T;
}
}
pending = ..(cacheKey);
(pending) {
.();
pending <T>;
}
requestPromise = .<T>(url, cacheKey);
..(cacheKey, requestPromise);
{
requestPromise;
} {
..(cacheKey);
}
}
makeRequest<T>(: , : ): <T> {
cached = ..(cacheKey);
: <, > = {};
(cached?.) {
headers[] = cached.;
}
response = ({
url,
headers,
: ,
});
(response. === && cached) {
.();
..(cacheKey, {
...cached,
: .(),
});
cached. T;
}
etag = response.[];
..(cacheKey, {
: response.,
: .(),
etag,
});
.();
response. T;
}
(): {
..();
}
(): { : ; : } {
size = ;
( entry ..()) {
size += .(entry.).;
}
{ size, : .. };
}
}
Step 2: Request Batching
export class BatchRequester<T, R> {
private queue: Array<{ input: T; resolve: (r: R) => void; reject: (e: Error) => void }> = [];
private batchTimeout: NodeJS.Timeout | null = null;
private batchProcessor: (inputs: T[]) => Promise<R[]>;
private maxBatchSize: number;
private batchDelayMs: number;
constructor(
batchProcessor: (inputs: T[]) => Promise<R[]>,
options: { maxBatchSize?: number; batchDelayMs?: number } = {}
) {
this.batchProcessor = batchProcessor;
this.maxBatchSize = options.maxBatchSize ?? 20;
this.batchDelayMs = options.batchDelayMs ?? ;
}
(: T): <R> {
( {
..({ input, resolve, reject });
(.. >= .) {
.();
} (!.) {
. = ( .(), .);
}
});
}
(): <> {
(.) {
(.);
. = ;
}
batch = ..(, .);
(batch. === ) ;
{
inputs = batch.( item.);
results = .(inputs);
batch.( {
item.(results[index]);
});
} (error) {
batch.( {
item.(error );
});
}
}
}
batcher = <, >(
(: []) => {
response = api.(noteIds);
response.;
}
);
note1 = batcher.();
note2 = batcher.();
Step 3: Storage Optimization
export class StorageOptimizer {
static compress(data: any): string {
const json = JSON.stringify(data);
return json
.replace(/\s+/g, '')
.replace(/"([^"]+)":/g, (_, key) => {
const shortKeys: Record<string, string> = {
'path': 'p',
'name': 'n',
'content': 'c',
'modified': 'm',
'created': 'r',
'tags': 't',
};
return `"${shortKeys[key] || key}":`;
});
}
static decompress(compressed: string): {
: <, > = {
: ,
: ,
: ,
: ,
: ,
: ,
};
expanded = compressed.(, {
;
});
.(expanded);
}
deduplicateStrings<T >(: T[]): { : T[]; : [] } {
: [] = [];
stringIndex = <, >();
indexedData = data.( {
: = {};
( [key, value] .(item)) {
( value === && value. > ) {
index = stringIndex.(value);
(index === ) {
index = dictionary.;
dictionary.(value);
stringIndex.(value, index);
}
indexed[key] = ;
} {
indexed[key] = value;
}
}
indexed;
});
{ : indexedData, dictionary };
}
(: , : ): {
: ;
: ;
: ;
} {
originalSize = .(original).;
optimizedSize = .(optimized).;
savingsPercent = ((originalSize - optimizedSize) / originalSize) * ;
{ originalSize, optimizedSize, savingsPercent };
}
}
Step 4: Rate Limiting with Quotas
interface QuotaConfig {
dailyLimit: number;
monthlyLimit: number;
perMinuteLimit: number;
}
export class QuotaManager {
private config: QuotaConfig;
private usage: {
daily: { count: number; date: string };
monthly: { count: number; month: string };
perMinute: number[];
};
private storageKey: string;
constructor(plugin: Plugin, config: QuotaConfig) {
this.config = config;
this.storageKey = 'api-quota-usage';
this.usage = this.loadUsage(plugin);
}
private loadUsage(plugin: Plugin): typeof this. {
saved = plugin.()?.[.];
today = ().().()[];
month = today.(, );
{
: saved?.?. === today
? saved.
: { : , : today },
: saved?.?. === month
? saved.
: { : , month },
: [],
};
}
(): <{ : ; ?: ; ?: }> {
now = .();
.. = ...( now - t < );
(... >= ..) {
oldestRequest = ..[];
waitMs = - (now - oldestRequest);
{
: ,
: ,
waitMs,
};
}
(... >= ..) {
{
: ,
: ,
};
}
(... >= ..) {
{
: ,
: ,
};
}
{ : };
}
(): {
now = .();
...(now);
...++;
...++;
}
(): {
: { : ; : ; : };
: { : ; : ; : };
} {
{
: {
: ...,
: ..,
: (... / ..) * ,
},
: {
: ...,
: ..,
: (... / ..) * ,
},
};
}
}
Step 5: Intelligent Sync Optimization
export class SyncOptimizer {
private lastSyncHashes = new Map<string, string>();
async getChangedFiles(
files: TFile[],
vault: Vault
): Promise<TFile[]> {
const changed: TFile[] = [];
for (const file of files) {
const content = await vault.cachedRead(file);
const hash = await this.hashContent(content);
const lastHash = this.lastSyncHashes.get(file.path);
if (hash !== lastHash) {
changed.push(file);
this.lastSyncHashes.set(file.path, hash);
}
}
console.log(`[Sync] ${changed.length}/${files.length} files changed`);
return changed;
}
private (: ): <> {
encoder = ();
data = encoder.(content);
hashBuffer = crypto..(, data);
hashArray = .( (hashBuffer));
hashArray.( b.().(, )).().(, );
}
(: ): { : ; : ; : } {
original = .(data);
compressed = .(data);
{
: compressed,
: original.,
: compressed.,
};
}
}
Output
- API request caching and deduplication
- Request batching for efficiency
- Storage compression and optimization
- Quota management with usage tracking
- Intelligent sync with change detection
Error Handling
| Issue | Cause | Solution |
|---|
| Quota exceeded | Too many requests | Implement rate limiting |
| High storage costs | Uncompressed data | Apply compression |
| Slow sync | Full sync every time | Use delta sync |
| API costs high | No caching | Add request caching |
Examples
Usage Dashboard Component
class UsageDashboard {
displayUsage(quotaManager: QuotaManager): HTMLElement {
const container = document.createElement('div');
const stats = quotaManager.getUsageStats();
container.innerHTML = `
<h4>API Usage</h4>
<div class="usage-bar">
<div class="usage-fill" style="width: ${stats.daily.percent}%"></div>
<span>Daily: ${stats.daily.used}/${stats.daily.limit}</span>
</div>
<div class="usage-bar">
<div class="usage-fill" style="width: ${stats.monthly.percent}%"></div>
<span>Monthly: ${stats.monthly.used}/${stats.monthly.limit}</span>
</div>
`;
return container;
}
}
Resources
Next Steps
For architecture patterns, see obsidian-reference-architecture.