Alle Event-Typen (file, content, nav, editor, vault, plugin, system), ContentSnapshot/Delta, QueryConfig, ProjectionTemplate, LogfireSettings mit Defaults, deepMerge-Utility. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
348 lines
8.5 KiB
TypeScript
348 lines
8.5 KiB
TypeScript
// ---------------------------------------------------------------------------
|
|
// Event System
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export type EventCategory =
|
|
| 'file'
|
|
| 'content'
|
|
| 'navigation'
|
|
| 'editor'
|
|
| 'vault'
|
|
| 'plugin'
|
|
| 'system';
|
|
|
|
export type EventType =
|
|
// File events
|
|
| 'file:create'
|
|
| 'file:delete'
|
|
| 'file:rename'
|
|
| 'file:move'
|
|
| 'file:modify'
|
|
// Content events (semantic diffs)
|
|
| 'content:words-changed'
|
|
| 'content:link-added'
|
|
| 'content:link-removed'
|
|
| 'content:tag-added'
|
|
| 'content:tag-removed'
|
|
| 'content:frontmatter-changed'
|
|
| 'content:heading-changed'
|
|
| 'content:embed-added'
|
|
| 'content:embed-removed'
|
|
// Navigation events
|
|
| 'nav:file-open'
|
|
| 'nav:file-close'
|
|
| 'nav:active-leaf-change'
|
|
// Editor events
|
|
| 'editor:change'
|
|
| 'editor:selection'
|
|
| 'editor:paste'
|
|
// Vault structure events
|
|
| 'vault:folder-create'
|
|
| 'vault:folder-delete'
|
|
| 'vault:folder-rename'
|
|
// System events
|
|
| 'system:session-start'
|
|
| 'system:session-end'
|
|
| 'system:baseline-scan'
|
|
| 'system:rescan'
|
|
| 'system:maintenance'
|
|
// Plugin events
|
|
| 'plugin:command-executed'
|
|
// Custom events (from other plugins via API)
|
|
| `custom:${string}`;
|
|
|
|
export interface LogfireEvent {
|
|
id: string;
|
|
timestamp: number;
|
|
type: EventType;
|
|
category: EventCategory;
|
|
source: string;
|
|
target?: string;
|
|
payload: Record<string, unknown>;
|
|
session: string;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Category mapping
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const CATEGORY_MAP: Record<string, EventCategory> = {
|
|
file: 'file',
|
|
content: 'content',
|
|
nav: 'navigation',
|
|
editor: 'editor',
|
|
vault: 'vault',
|
|
plugin: 'plugin',
|
|
system: 'system',
|
|
custom: 'plugin',
|
|
};
|
|
|
|
export function categoryForType(type: EventType): EventCategory {
|
|
const prefix = type.split(':')[0];
|
|
return CATEGORY_MAP[prefix] ?? 'system';
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Content Analysis
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export interface ContentSnapshot {
|
|
path: string;
|
|
wordCount: number;
|
|
charCount: number;
|
|
links: string[];
|
|
tags: string[];
|
|
headings: { level: number; text: string }[];
|
|
frontmatter: Record<string, unknown>;
|
|
embeds: string[];
|
|
}
|
|
|
|
export interface ContentDelta {
|
|
wordsAdded: number;
|
|
wordsRemoved: number;
|
|
linksAdded: string[];
|
|
linksRemoved: string[];
|
|
tagsAdded: string[];
|
|
tagsRemoved: string[];
|
|
headingsAdded: { level: number; text: string }[];
|
|
headingsRemoved: { level: number; text: string }[];
|
|
embedsAdded: string[];
|
|
embedsRemoved: string[];
|
|
frontmatterChanged: boolean;
|
|
frontmatterChanges: Record<string, { old: unknown; new: unknown }>;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Query System
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export type TimeRange =
|
|
| { type: 'relative'; value: 'today' | 'yesterday' | 'this-week' | 'this-month' | 'last-7-days' | 'last-30-days' }
|
|
| { type: 'absolute'; from: number; to: number }
|
|
| { type: 'session'; sessionId?: string };
|
|
|
|
export interface QueryConfig {
|
|
timeRange: TimeRange;
|
|
eventTypes?: EventType[];
|
|
categories?: EventCategory[];
|
|
filePaths?: string[];
|
|
groupBy?: 'file' | 'type' | 'hour' | 'day' | 'session';
|
|
orderBy?: 'timestamp' | 'count' | 'words';
|
|
orderDirection?: 'asc' | 'desc';
|
|
limit?: number;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Projections
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export interface ProjectionTemplate {
|
|
id: string;
|
|
name: string;
|
|
description: string;
|
|
enabled: boolean;
|
|
trigger: ProjectionTrigger;
|
|
output: OutputConfig;
|
|
sections: SectionConfig[];
|
|
}
|
|
|
|
export interface ProjectionTrigger {
|
|
type: 'interval' | 'on-session-end' | 'manual' | 'cron';
|
|
interval?: number;
|
|
cron?: string;
|
|
}
|
|
|
|
export interface OutputConfig {
|
|
folder: string;
|
|
filePattern: string;
|
|
mode: 'overwrite' | 'append';
|
|
frontmatter: Record<string, string>;
|
|
}
|
|
|
|
export interface SectionConfig {
|
|
id: string;
|
|
heading: string;
|
|
type: SectionType;
|
|
query: QueryConfig;
|
|
format: FormatConfig;
|
|
enabled: boolean;
|
|
}
|
|
|
|
export type SectionType = 'timeline' | 'table' | 'summary' | 'chart-data' | 'raw-events' | 'custom';
|
|
|
|
export interface FormatConfig {
|
|
type: 'builtin' | 'custom';
|
|
builtin?: BuiltinFormat;
|
|
customTemplate?: string;
|
|
}
|
|
|
|
export type BuiltinFormat =
|
|
| { name: 'timeline'; showTimestamp: boolean; showPayload: boolean }
|
|
| { name: 'table'; columns: ColumnDef[] }
|
|
| { name: 'summary'; metrics: MetricDef[] }
|
|
| { name: 'metric'; aggregate: 'count' | 'sum' | 'avg' | 'min' | 'max'; field?: string };
|
|
|
|
export interface ColumnDef {
|
|
header: string;
|
|
value: string;
|
|
align?: 'left' | 'right' | 'center';
|
|
}
|
|
|
|
export interface MetricDef {
|
|
label: string;
|
|
aggregate: 'count' | 'sum' | 'avg' | 'min' | 'max';
|
|
field?: string;
|
|
filter?: Partial<QueryConfig>;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Settings
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export interface LogfireSettings {
|
|
settingsVersion: number;
|
|
|
|
general: {
|
|
logFolder: string;
|
|
enabled: boolean;
|
|
pauseOnStartup: boolean;
|
|
};
|
|
|
|
tracking: {
|
|
fileEvents: boolean;
|
|
contentAnalysis: boolean;
|
|
navigation: boolean;
|
|
editorChanges: boolean;
|
|
commandTracking: boolean;
|
|
excludedFolders: string[];
|
|
excludedPatterns: string[];
|
|
};
|
|
|
|
projections: {
|
|
templates: ProjectionTemplate[];
|
|
};
|
|
|
|
advanced: {
|
|
debounceMs: number;
|
|
flushIntervalMs: number;
|
|
flushThreshold: number;
|
|
retention: RetentionSettings;
|
|
database: DatabaseSettings;
|
|
};
|
|
}
|
|
|
|
export interface RetentionSettings {
|
|
rawEventsDays: number;
|
|
dailyStatsDays: number;
|
|
monthlyStatsForever: boolean;
|
|
neverDeleteTypes: EventType[];
|
|
maintenanceOnStartup: boolean;
|
|
}
|
|
|
|
export interface DatabaseSettings {
|
|
cacheSizeMb: number;
|
|
walMode: boolean;
|
|
mmapSizeMb: number;
|
|
}
|
|
|
|
export const DEFAULT_SETTINGS: LogfireSettings = {
|
|
settingsVersion: 1,
|
|
|
|
general: {
|
|
logFolder: 'Logfire',
|
|
enabled: true,
|
|
pauseOnStartup: false,
|
|
},
|
|
|
|
tracking: {
|
|
fileEvents: true,
|
|
contentAnalysis: true,
|
|
navigation: true,
|
|
editorChanges: true,
|
|
commandTracking: true,
|
|
excludedFolders: ['.obsidian'],
|
|
excludedPatterns: [],
|
|
},
|
|
|
|
projections: {
|
|
templates: [],
|
|
},
|
|
|
|
advanced: {
|
|
debounceMs: 3000,
|
|
flushIntervalMs: 5000,
|
|
flushThreshold: 100,
|
|
retention: {
|
|
rawEventsDays: 30,
|
|
dailyStatsDays: 365,
|
|
monthlyStatsForever: true,
|
|
neverDeleteTypes: ['file:create', 'file:delete', 'file:rename'],
|
|
maintenanceOnStartup: true,
|
|
},
|
|
database: {
|
|
cacheSizeMb: 64,
|
|
walMode: true,
|
|
mmapSizeMb: 256,
|
|
},
|
|
},
|
|
};
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// API types
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export interface VaultStats {
|
|
eventsTotal: number;
|
|
wordsAdded: number;
|
|
wordsRemoved: number;
|
|
filesCreated: number;
|
|
filesModified: number;
|
|
filesDeleted: number;
|
|
sessionCount: number;
|
|
activeTimeMs: number;
|
|
}
|
|
|
|
export interface FileStats {
|
|
path: string;
|
|
eventsTotal: number;
|
|
wordsAdded: number;
|
|
wordsRemoved: number;
|
|
activeTimeMs: number;
|
|
lastModified: number;
|
|
editSessions: number;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Utilities
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export function deepMerge<T>(defaults: T, overrides: Partial<T>): T {
|
|
if (
|
|
typeof defaults !== 'object' || defaults === null ||
|
|
typeof overrides !== 'object' || overrides === null
|
|
) {
|
|
return (overrides ?? defaults) as T;
|
|
}
|
|
|
|
const result = { ...defaults } as Record<string, unknown>;
|
|
const src = overrides as Record<string, unknown>;
|
|
const def = defaults as Record<string, unknown>;
|
|
|
|
for (const key of Object.keys(src)) {
|
|
const defaultVal = def[key];
|
|
const overrideVal = src[key];
|
|
|
|
if (
|
|
defaultVal !== null && defaultVal !== undefined &&
|
|
overrideVal !== null && overrideVal !== undefined &&
|
|
typeof defaultVal === 'object' && !Array.isArray(defaultVal) &&
|
|
typeof overrideVal === 'object' && !Array.isArray(overrideVal)
|
|
) {
|
|
result[key] = deepMerge(defaultVal, overrideVal);
|
|
} else if (overrideVal !== undefined) {
|
|
result[key] = overrideVal;
|
|
}
|
|
}
|
|
|
|
return result as T;
|
|
}
|