From d5dfdd6b0d2f48521c3f4c82cd84c4e6e03110be Mon Sep 17 00:00:00 2001 From: tolvitty Date: Thu, 12 Feb 2026 10:45:49 +0100 Subject: [PATCH] Kern-Typen: Event-System, Settings, Query-Interfaces 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 --- src/types.ts | 348 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 348 insertions(+) create mode 100644 src/types.ts diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..9801d7d --- /dev/null +++ b/src/types.ts @@ -0,0 +1,348 @@ +// --------------------------------------------------------------------------- +// 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; + session: string; +} + +// --------------------------------------------------------------------------- +// Category mapping +// --------------------------------------------------------------------------- + +const CATEGORY_MAP: Record = { + 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; + 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; +} + +// --------------------------------------------------------------------------- +// 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; +} + +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; +} + +// --------------------------------------------------------------------------- +// 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(defaults: T, overrides: Partial): T { + if ( + typeof defaults !== 'object' || defaults === null || + typeof overrides !== 'object' || overrides === null + ) { + return (overrides ?? defaults) as T; + } + + const result = { ...defaults } as Record; + const src = overrides as Record; + const def = defaults as Record; + + 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; +}