driftdetect-core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analyzers/ast-analyzer.d.ts +251 -0
- package/dist/analyzers/ast-analyzer.d.ts.map +1 -0
- package/dist/analyzers/ast-analyzer.js +548 -0
- package/dist/analyzers/ast-analyzer.js.map +1 -0
- package/dist/analyzers/flow-analyzer.d.ts +241 -0
- package/dist/analyzers/flow-analyzer.d.ts.map +1 -0
- package/dist/analyzers/flow-analyzer.js +1219 -0
- package/dist/analyzers/flow-analyzer.js.map +1 -0
- package/dist/analyzers/index.d.ts +18 -0
- package/dist/analyzers/index.d.ts.map +1 -0
- package/dist/analyzers/index.js +19 -0
- package/dist/analyzers/index.js.map +1 -0
- package/dist/analyzers/semantic-analyzer.d.ts +252 -0
- package/dist/analyzers/semantic-analyzer.d.ts.map +1 -0
- package/dist/analyzers/semantic-analyzer.js +1182 -0
- package/dist/analyzers/semantic-analyzer.js.map +1 -0
- package/dist/analyzers/type-analyzer.d.ts +289 -0
- package/dist/analyzers/type-analyzer.d.ts.map +1 -0
- package/dist/analyzers/type-analyzer.js +1269 -0
- package/dist/analyzers/type-analyzer.js.map +1 -0
- package/dist/analyzers/types.d.ts +537 -0
- package/dist/analyzers/types.d.ts.map +1 -0
- package/dist/analyzers/types.js +11 -0
- package/dist/analyzers/types.js.map +1 -0
- package/dist/config/config-loader.d.ts +166 -0
- package/dist/config/config-loader.d.ts.map +1 -0
- package/dist/config/config-loader.js +429 -0
- package/dist/config/config-loader.js.map +1 -0
- package/dist/config/config-validator.d.ts +204 -0
- package/dist/config/config-validator.d.ts.map +1 -0
- package/dist/config/config-validator.js +632 -0
- package/dist/config/config-validator.js.map +1 -0
- package/dist/config/defaults.d.ts +8 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +26 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/index.d.ts +10 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +10 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/types.d.ts +47 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +7 -0
- package/dist/config/types.js.map +1 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest/exporter.d.ts +21 -0
- package/dist/manifest/exporter.d.ts.map +1 -0
- package/dist/manifest/exporter.js +339 -0
- package/dist/manifest/exporter.js.map +1 -0
- package/dist/manifest/index.d.ts +14 -0
- package/dist/manifest/index.d.ts.map +1 -0
- package/dist/manifest/index.js +15 -0
- package/dist/manifest/index.js.map +1 -0
- package/dist/manifest/manifest-store.d.ts +111 -0
- package/dist/manifest/manifest-store.d.ts.map +1 -0
- package/dist/manifest/manifest-store.js +418 -0
- package/dist/manifest/manifest-store.js.map +1 -0
- package/dist/manifest/types.d.ts +238 -0
- package/dist/manifest/types.d.ts.map +1 -0
- package/dist/manifest/types.js +11 -0
- package/dist/manifest/types.js.map +1 -0
- package/dist/matcher/confidence-scorer.d.ts +188 -0
- package/dist/matcher/confidence-scorer.d.ts.map +1 -0
- package/dist/matcher/confidence-scorer.js +302 -0
- package/dist/matcher/confidence-scorer.js.map +1 -0
- package/dist/matcher/index.d.ts +24 -0
- package/dist/matcher/index.d.ts.map +1 -0
- package/dist/matcher/index.js +26 -0
- package/dist/matcher/index.js.map +1 -0
- package/dist/matcher/outlier-detector.d.ts +252 -0
- package/dist/matcher/outlier-detector.d.ts.map +1 -0
- package/dist/matcher/outlier-detector.js +544 -0
- package/dist/matcher/outlier-detector.js.map +1 -0
- package/dist/matcher/pattern-matcher.d.ts +169 -0
- package/dist/matcher/pattern-matcher.d.ts.map +1 -0
- package/dist/matcher/pattern-matcher.js +692 -0
- package/dist/matcher/pattern-matcher.js.map +1 -0
- package/dist/matcher/types.d.ts +476 -0
- package/dist/matcher/types.d.ts.map +1 -0
- package/dist/matcher/types.js +36 -0
- package/dist/matcher/types.js.map +1 -0
- package/dist/parsers/base-parser.d.ts +282 -0
- package/dist/parsers/base-parser.d.ts.map +1 -0
- package/dist/parsers/base-parser.js +421 -0
- package/dist/parsers/base-parser.js.map +1 -0
- package/dist/parsers/css-parser.d.ts +225 -0
- package/dist/parsers/css-parser.d.ts.map +1 -0
- package/dist/parsers/css-parser.js +477 -0
- package/dist/parsers/css-parser.js.map +1 -0
- package/dist/parsers/index.d.ts +15 -0
- package/dist/parsers/index.d.ts.map +1 -0
- package/dist/parsers/index.js +15 -0
- package/dist/parsers/index.js.map +1 -0
- package/dist/parsers/json-parser.d.ts +219 -0
- package/dist/parsers/json-parser.d.ts.map +1 -0
- package/dist/parsers/json-parser.js +602 -0
- package/dist/parsers/json-parser.js.map +1 -0
- package/dist/parsers/markdown-parser.d.ts +276 -0
- package/dist/parsers/markdown-parser.d.ts.map +1 -0
- package/dist/parsers/markdown-parser.js +731 -0
- package/dist/parsers/markdown-parser.js.map +1 -0
- package/dist/parsers/parser-manager.d.ts +294 -0
- package/dist/parsers/parser-manager.d.ts.map +1 -0
- package/dist/parsers/parser-manager.js +738 -0
- package/dist/parsers/parser-manager.js.map +1 -0
- package/dist/parsers/python-parser.d.ts +204 -0
- package/dist/parsers/python-parser.d.ts.map +1 -0
- package/dist/parsers/python-parser.js +517 -0
- package/dist/parsers/python-parser.js.map +1 -0
- package/dist/parsers/types.d.ts +43 -0
- package/dist/parsers/types.d.ts.map +1 -0
- package/dist/parsers/types.js +7 -0
- package/dist/parsers/types.js.map +1 -0
- package/dist/parsers/typescript-parser.d.ts +264 -0
- package/dist/parsers/typescript-parser.d.ts.map +1 -0
- package/dist/parsers/typescript-parser.js +658 -0
- package/dist/parsers/typescript-parser.js.map +1 -0
- package/dist/rules/evaluator.d.ts +305 -0
- package/dist/rules/evaluator.d.ts.map +1 -0
- package/dist/rules/evaluator.js +579 -0
- package/dist/rules/evaluator.js.map +1 -0
- package/dist/rules/index.d.ts +13 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +13 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/quick-fix-generator.d.ts +334 -0
- package/dist/rules/quick-fix-generator.d.ts.map +1 -0
- package/dist/rules/quick-fix-generator.js +1075 -0
- package/dist/rules/quick-fix-generator.js.map +1 -0
- package/dist/rules/rule-engine.d.ts +241 -0
- package/dist/rules/rule-engine.d.ts.map +1 -0
- package/dist/rules/rule-engine.js +585 -0
- package/dist/rules/rule-engine.js.map +1 -0
- package/dist/rules/severity-manager.d.ts +394 -0
- package/dist/rules/severity-manager.d.ts.map +1 -0
- package/dist/rules/severity-manager.js +619 -0
- package/dist/rules/severity-manager.js.map +1 -0
- package/dist/rules/types.d.ts +370 -0
- package/dist/rules/types.d.ts.map +1 -0
- package/dist/rules/types.js +133 -0
- package/dist/rules/types.js.map +1 -0
- package/dist/rules/variant-manager.d.ts +388 -0
- package/dist/rules/variant-manager.d.ts.map +1 -0
- package/dist/rules/variant-manager.js +777 -0
- package/dist/rules/variant-manager.js.map +1 -0
- package/dist/scanner/change-detector.d.ts +164 -0
- package/dist/scanner/change-detector.d.ts.map +1 -0
- package/dist/scanner/change-detector.js +263 -0
- package/dist/scanner/change-detector.js.map +1 -0
- package/dist/scanner/dependency-graph.d.ts +270 -0
- package/dist/scanner/dependency-graph.d.ts.map +1 -0
- package/dist/scanner/dependency-graph.js +436 -0
- package/dist/scanner/dependency-graph.js.map +1 -0
- package/dist/scanner/file-walker.d.ts +127 -0
- package/dist/scanner/file-walker.d.ts.map +1 -0
- package/dist/scanner/file-walker.js +526 -0
- package/dist/scanner/file-walker.js.map +1 -0
- package/dist/scanner/index.d.ts +12 -0
- package/dist/scanner/index.d.ts.map +1 -0
- package/dist/scanner/index.js +12 -0
- package/dist/scanner/index.js.map +1 -0
- package/dist/scanner/types.d.ts +218 -0
- package/dist/scanner/types.d.ts.map +1 -0
- package/dist/scanner/types.js +10 -0
- package/dist/scanner/types.js.map +1 -0
- package/dist/scanner/worker-pool.d.ts +317 -0
- package/dist/scanner/worker-pool.d.ts.map +1 -0
- package/dist/scanner/worker-pool.js +571 -0
- package/dist/scanner/worker-pool.js.map +1 -0
- package/dist/store/cache-manager.d.ts +179 -0
- package/dist/store/cache-manager.d.ts.map +1 -0
- package/dist/store/cache-manager.js +391 -0
- package/dist/store/cache-manager.js.map +1 -0
- package/dist/store/history-store.d.ts +314 -0
- package/dist/store/history-store.d.ts.map +1 -0
- package/dist/store/history-store.js +707 -0
- package/dist/store/history-store.js.map +1 -0
- package/dist/store/index.d.ts +20 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +26 -0
- package/dist/store/index.js.map +1 -0
- package/dist/store/lock-file-manager.d.ts +202 -0
- package/dist/store/lock-file-manager.d.ts.map +1 -0
- package/dist/store/lock-file-manager.js +475 -0
- package/dist/store/lock-file-manager.js.map +1 -0
- package/dist/store/pattern-store.d.ts +289 -0
- package/dist/store/pattern-store.d.ts.map +1 -0
- package/dist/store/pattern-store.js +936 -0
- package/dist/store/pattern-store.js.map +1 -0
- package/dist/store/schema-validator.d.ts +159 -0
- package/dist/store/schema-validator.d.ts.map +1 -0
- package/dist/store/schema-validator.js +1096 -0
- package/dist/store/schema-validator.js.map +1 -0
- package/dist/store/types.d.ts +585 -0
- package/dist/store/types.d.ts.map +1 -0
- package/dist/store/types.js +82 -0
- package/dist/store/types.js.map +1 -0
- package/dist/types/analysis.d.ts +19 -0
- package/dist/types/analysis.d.ts.map +1 -0
- package/dist/types/analysis.js +5 -0
- package/dist/types/analysis.js.map +1 -0
- package/dist/types/common.d.ts +7 -0
- package/dist/types/common.d.ts.map +1 -0
- package/dist/types/common.js +5 -0
- package/dist/types/common.js.map +1 -0
- package/dist/types/index.d.ts +12 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +10 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/patterns.d.ts +40 -0
- package/dist/types/patterns.d.ts.map +1 -0
- package/dist/types/patterns.js +7 -0
- package/dist/types/patterns.js.map +1 -0
- package/dist/types/violations.d.ts +7 -0
- package/dist/types/violations.d.ts.map +1 -0
- package/dist/types/violations.js +7 -0
- package/dist/types/violations.js.map +1 -0
- package/package.json +46 -0
|
@@ -0,0 +1,707 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* History Store - Pattern change tracking and approval history
|
|
3
|
+
*
|
|
4
|
+
* Tracks pattern changes over time and stores approval history.
|
|
5
|
+
* History entries are stored in .drift/history/ directory.
|
|
6
|
+
*
|
|
7
|
+
* @requirements 4.4 - THE Pattern_Store SHALL maintain history of pattern changes in .drift/history/
|
|
8
|
+
*/
|
|
9
|
+
import * as fs from 'node:fs/promises';
|
|
10
|
+
import * as path from 'node:path';
|
|
11
|
+
import { EventEmitter } from 'node:events';
|
|
12
|
+
import { HISTORY_FILE_VERSION } from './types.js';
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// Constants
|
|
15
|
+
// ============================================================================
|
|
16
|
+
/** Directory name for drift configuration */
|
|
17
|
+
const DRIFT_DIR = '.drift';
|
|
18
|
+
/** Directory name for history */
|
|
19
|
+
const HISTORY_DIR = 'history';
|
|
20
|
+
/** Main history file name */
|
|
21
|
+
const HISTORY_FILE = 'patterns.json';
|
|
22
|
+
/** Default maximum history entries per pattern */
|
|
23
|
+
const DEFAULT_MAX_ENTRIES_PER_PATTERN = 100;
|
|
24
|
+
/** Default maximum age for history entries in days */
|
|
25
|
+
const DEFAULT_MAX_AGE_DAYS = 365;
|
|
26
|
+
// ============================================================================
|
|
27
|
+
// Error Classes
|
|
28
|
+
// ============================================================================
|
|
29
|
+
/**
|
|
30
|
+
* Error thrown when a history operation fails
|
|
31
|
+
*/
|
|
32
|
+
export class HistoryStoreError extends Error {
|
|
33
|
+
errorCause;
|
|
34
|
+
constructor(message, errorCause) {
|
|
35
|
+
super(message);
|
|
36
|
+
this.name = 'HistoryStoreError';
|
|
37
|
+
this.errorCause = errorCause;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Error thrown when a pattern history is not found
|
|
42
|
+
*/
|
|
43
|
+
export class PatternHistoryNotFoundError extends Error {
|
|
44
|
+
patternId;
|
|
45
|
+
constructor(patternId) {
|
|
46
|
+
super(`Pattern history not found: ${patternId}`);
|
|
47
|
+
this.patternId = patternId;
|
|
48
|
+
this.name = 'PatternHistoryNotFoundError';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Default history store configuration
|
|
53
|
+
*/
|
|
54
|
+
export const DEFAULT_HISTORY_STORE_CONFIG = {
|
|
55
|
+
rootDir: '.',
|
|
56
|
+
maxEntriesPerPattern: DEFAULT_MAX_ENTRIES_PER_PATTERN,
|
|
57
|
+
maxAgeDays: DEFAULT_MAX_AGE_DAYS,
|
|
58
|
+
autoSave: false,
|
|
59
|
+
autoSaveDebounce: 1000,
|
|
60
|
+
};
|
|
61
|
+
// ============================================================================
|
|
62
|
+
// Helper Functions
|
|
63
|
+
// ============================================================================
|
|
64
|
+
/**
|
|
65
|
+
* Check if a file exists
|
|
66
|
+
*/
|
|
67
|
+
async function fileExists(filePath) {
|
|
68
|
+
try {
|
|
69
|
+
await fs.access(filePath);
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Ensure a directory exists
|
|
78
|
+
*/
|
|
79
|
+
async function ensureDir(dirPath) {
|
|
80
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
81
|
+
}
|
|
82
|
+
// ============================================================================
|
|
83
|
+
// History Store Class
|
|
84
|
+
// ============================================================================
|
|
85
|
+
/**
|
|
86
|
+
* History Store - Manages pattern change history
|
|
87
|
+
*
|
|
88
|
+
* Tracks all pattern changes including:
|
|
89
|
+
* - Pattern created
|
|
90
|
+
* - Pattern updated
|
|
91
|
+
* - Pattern approved
|
|
92
|
+
* - Pattern ignored
|
|
93
|
+
* - Pattern deleted
|
|
94
|
+
* - Confidence changed
|
|
95
|
+
* - Locations changed
|
|
96
|
+
* - Severity changed
|
|
97
|
+
*
|
|
98
|
+
* History is stored in .drift/history/patterns.json
|
|
99
|
+
*
|
|
100
|
+
* @requirements 4.4 - Pattern history tracked in .drift/history/
|
|
101
|
+
*/
|
|
102
|
+
export class HistoryStore extends EventEmitter {
|
|
103
|
+
config;
|
|
104
|
+
historyDir;
|
|
105
|
+
historyFilePath;
|
|
106
|
+
histories = new Map();
|
|
107
|
+
loaded = false;
|
|
108
|
+
dirty = false;
|
|
109
|
+
saveTimeout = null;
|
|
110
|
+
constructor(config = {}) {
|
|
111
|
+
super();
|
|
112
|
+
this.config = { ...DEFAULT_HISTORY_STORE_CONFIG, ...config };
|
|
113
|
+
this.historyDir = path.join(this.config.rootDir, DRIFT_DIR, HISTORY_DIR);
|
|
114
|
+
this.historyFilePath = path.join(this.historyDir, HISTORY_FILE);
|
|
115
|
+
}
|
|
116
|
+
// ==========================================================================
|
|
117
|
+
// Initialization
|
|
118
|
+
// ==========================================================================
|
|
119
|
+
/**
|
|
120
|
+
* Initialize the history store
|
|
121
|
+
*
|
|
122
|
+
* Creates necessary directories and loads existing history.
|
|
123
|
+
*/
|
|
124
|
+
async initialize() {
|
|
125
|
+
// Create directory structure
|
|
126
|
+
await ensureDir(this.historyDir);
|
|
127
|
+
// Load existing history
|
|
128
|
+
await this.load();
|
|
129
|
+
this.loaded = true;
|
|
130
|
+
}
|
|
131
|
+
// ==========================================================================
|
|
132
|
+
// Loading
|
|
133
|
+
// ==========================================================================
|
|
134
|
+
/**
|
|
135
|
+
* Load history from disk
|
|
136
|
+
*
|
|
137
|
+
* @requirements 4.4 - Load history from .drift/history/
|
|
138
|
+
*/
|
|
139
|
+
async load() {
|
|
140
|
+
this.histories.clear();
|
|
141
|
+
if (!(await fileExists(this.historyFilePath))) {
|
|
142
|
+
this.emitEvent('file:loaded', undefined, { count: 0 });
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
const content = await fs.readFile(this.historyFilePath, 'utf-8');
|
|
147
|
+
const data = JSON.parse(content);
|
|
148
|
+
// Load pattern histories into map
|
|
149
|
+
for (const history of data.patterns) {
|
|
150
|
+
this.histories.set(history.patternId, history);
|
|
151
|
+
}
|
|
152
|
+
this.emitEvent('file:loaded', undefined, { count: this.histories.size });
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
if (error.code === 'ENOENT') {
|
|
156
|
+
return; // File doesn't exist, skip
|
|
157
|
+
}
|
|
158
|
+
throw new HistoryStoreError(`Failed to load history file: ${this.historyFilePath}`, error);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// ==========================================================================
|
|
162
|
+
// Saving
|
|
163
|
+
// ==========================================================================
|
|
164
|
+
/**
|
|
165
|
+
* Save history to disk
|
|
166
|
+
*
|
|
167
|
+
* @requirements 4.4 - Persist history in .drift/history/
|
|
168
|
+
*/
|
|
169
|
+
async save() {
|
|
170
|
+
const historyFile = {
|
|
171
|
+
version: HISTORY_FILE_VERSION,
|
|
172
|
+
patterns: Array.from(this.histories.values()),
|
|
173
|
+
lastUpdated: new Date().toISOString(),
|
|
174
|
+
};
|
|
175
|
+
// Ensure directory exists
|
|
176
|
+
await ensureDir(this.historyDir);
|
|
177
|
+
// Write file
|
|
178
|
+
await fs.writeFile(this.historyFilePath, JSON.stringify(historyFile, null, 2));
|
|
179
|
+
this.dirty = false;
|
|
180
|
+
this.emitEvent('file:saved', undefined, { count: this.histories.size });
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Schedule an auto-save if enabled
|
|
184
|
+
*/
|
|
185
|
+
scheduleAutoSave() {
|
|
186
|
+
if (!this.config.autoSave) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (this.saveTimeout) {
|
|
190
|
+
clearTimeout(this.saveTimeout);
|
|
191
|
+
}
|
|
192
|
+
this.saveTimeout = setTimeout(async () => {
|
|
193
|
+
if (this.dirty) {
|
|
194
|
+
await this.save();
|
|
195
|
+
}
|
|
196
|
+
}, this.config.autoSaveDebounce);
|
|
197
|
+
}
|
|
198
|
+
// ==========================================================================
|
|
199
|
+
// Recording Events
|
|
200
|
+
// ==========================================================================
|
|
201
|
+
/**
|
|
202
|
+
* Record a history event for a pattern
|
|
203
|
+
*
|
|
204
|
+
* @param patternId - Pattern ID
|
|
205
|
+
* @param category - Pattern category
|
|
206
|
+
* @param eventType - Type of event
|
|
207
|
+
* @param options - Additional event options
|
|
208
|
+
*/
|
|
209
|
+
recordEvent(patternId, category, eventType, options = {}) {
|
|
210
|
+
const now = new Date().toISOString();
|
|
211
|
+
// Create the event
|
|
212
|
+
const event = {
|
|
213
|
+
timestamp: now,
|
|
214
|
+
type: eventType,
|
|
215
|
+
patternId,
|
|
216
|
+
...(options.user && { user: options.user }),
|
|
217
|
+
...(options.previousValue !== undefined && { previousValue: options.previousValue }),
|
|
218
|
+
...(options.newValue !== undefined && { newValue: options.newValue }),
|
|
219
|
+
...(options.details && { details: options.details }),
|
|
220
|
+
};
|
|
221
|
+
// Get or create pattern history
|
|
222
|
+
let history = this.histories.get(patternId);
|
|
223
|
+
if (!history) {
|
|
224
|
+
history = {
|
|
225
|
+
patternId,
|
|
226
|
+
category,
|
|
227
|
+
events: [],
|
|
228
|
+
createdAt: now,
|
|
229
|
+
lastModified: now,
|
|
230
|
+
};
|
|
231
|
+
this.histories.set(patternId, history);
|
|
232
|
+
}
|
|
233
|
+
// Add event to history
|
|
234
|
+
history.events.push(event);
|
|
235
|
+
history.lastModified = now;
|
|
236
|
+
// Prune if needed
|
|
237
|
+
this.prunePatternHistory(patternId);
|
|
238
|
+
this.dirty = true;
|
|
239
|
+
this.emitEvent('event:recorded', patternId, { eventType });
|
|
240
|
+
this.scheduleAutoSave();
|
|
241
|
+
return event;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Record a pattern creation event
|
|
245
|
+
*/
|
|
246
|
+
recordCreated(pattern, user) {
|
|
247
|
+
const options = {
|
|
248
|
+
newValue: {
|
|
249
|
+
name: pattern.name,
|
|
250
|
+
description: pattern.description,
|
|
251
|
+
confidence: pattern.confidence.score,
|
|
252
|
+
severity: pattern.severity,
|
|
253
|
+
},
|
|
254
|
+
};
|
|
255
|
+
if (user !== undefined) {
|
|
256
|
+
options.user = user;
|
|
257
|
+
}
|
|
258
|
+
return this.recordEvent(pattern.id, pattern.category, 'created', options);
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Record a pattern update event
|
|
262
|
+
*/
|
|
263
|
+
recordUpdated(pattern, previousPattern, user) {
|
|
264
|
+
const options = {
|
|
265
|
+
previousValue: {
|
|
266
|
+
name: previousPattern.name,
|
|
267
|
+
description: previousPattern.description,
|
|
268
|
+
},
|
|
269
|
+
newValue: {
|
|
270
|
+
name: pattern.name,
|
|
271
|
+
description: pattern.description,
|
|
272
|
+
},
|
|
273
|
+
};
|
|
274
|
+
if (user !== undefined) {
|
|
275
|
+
options.user = user;
|
|
276
|
+
}
|
|
277
|
+
return this.recordEvent(pattern.id, pattern.category, 'updated', options);
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Record a pattern approval event
|
|
281
|
+
*/
|
|
282
|
+
recordApproved(pattern, user) {
|
|
283
|
+
const options = {
|
|
284
|
+
details: {
|
|
285
|
+
confidence: pattern.confidence.score,
|
|
286
|
+
severity: pattern.severity,
|
|
287
|
+
},
|
|
288
|
+
};
|
|
289
|
+
if (user !== undefined) {
|
|
290
|
+
options.user = user;
|
|
291
|
+
}
|
|
292
|
+
return this.recordEvent(pattern.id, pattern.category, 'approved', options);
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Record a pattern ignore event
|
|
296
|
+
*/
|
|
297
|
+
recordIgnored(pattern, user) {
|
|
298
|
+
const options = {};
|
|
299
|
+
if (user !== undefined) {
|
|
300
|
+
options.user = user;
|
|
301
|
+
}
|
|
302
|
+
return this.recordEvent(pattern.id, pattern.category, 'ignored', options);
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Record a pattern deletion event
|
|
306
|
+
*/
|
|
307
|
+
recordDeleted(pattern, user) {
|
|
308
|
+
const options = {
|
|
309
|
+
previousValue: {
|
|
310
|
+
name: pattern.name,
|
|
311
|
+
status: pattern.status,
|
|
312
|
+
},
|
|
313
|
+
};
|
|
314
|
+
if (user !== undefined) {
|
|
315
|
+
options.user = user;
|
|
316
|
+
}
|
|
317
|
+
return this.recordEvent(pattern.id, pattern.category, 'deleted', options);
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Record a confidence change event
|
|
321
|
+
*/
|
|
322
|
+
recordConfidenceChanged(pattern, previousScore, user) {
|
|
323
|
+
const options = {
|
|
324
|
+
previousValue: previousScore,
|
|
325
|
+
newValue: pattern.confidence.score,
|
|
326
|
+
};
|
|
327
|
+
if (user !== undefined) {
|
|
328
|
+
options.user = user;
|
|
329
|
+
}
|
|
330
|
+
return this.recordEvent(pattern.id, pattern.category, 'confidence_changed', options);
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Record a locations change event
|
|
334
|
+
*/
|
|
335
|
+
recordLocationsChanged(pattern, previousCount, user) {
|
|
336
|
+
const options = {
|
|
337
|
+
previousValue: previousCount,
|
|
338
|
+
newValue: pattern.locations.length,
|
|
339
|
+
details: {
|
|
340
|
+
added: Math.max(0, pattern.locations.length - previousCount),
|
|
341
|
+
removed: Math.max(0, previousCount - pattern.locations.length),
|
|
342
|
+
},
|
|
343
|
+
};
|
|
344
|
+
if (user !== undefined) {
|
|
345
|
+
options.user = user;
|
|
346
|
+
}
|
|
347
|
+
return this.recordEvent(pattern.id, pattern.category, 'locations_changed', options);
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Record a severity change event
|
|
351
|
+
*/
|
|
352
|
+
recordSeverityChanged(pattern, previousSeverity, user) {
|
|
353
|
+
const options = {
|
|
354
|
+
previousValue: previousSeverity,
|
|
355
|
+
newValue: pattern.severity,
|
|
356
|
+
};
|
|
357
|
+
if (user !== undefined) {
|
|
358
|
+
options.user = user;
|
|
359
|
+
}
|
|
360
|
+
return this.recordEvent(pattern.id, pattern.category, 'severity_changed', options);
|
|
361
|
+
}
|
|
362
|
+
// ==========================================================================
|
|
363
|
+
// Querying
|
|
364
|
+
// ==========================================================================
|
|
365
|
+
/**
|
|
366
|
+
* Get history for a specific pattern
|
|
367
|
+
*
|
|
368
|
+
* @param patternId - Pattern ID
|
|
369
|
+
* @returns Pattern history or undefined if not found
|
|
370
|
+
*/
|
|
371
|
+
getPatternHistory(patternId) {
|
|
372
|
+
return this.histories.get(patternId);
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Get history for a specific pattern, throwing if not found
|
|
376
|
+
*
|
|
377
|
+
* @param patternId - Pattern ID
|
|
378
|
+
* @returns Pattern history
|
|
379
|
+
* @throws PatternHistoryNotFoundError if not found
|
|
380
|
+
*/
|
|
381
|
+
getPatternHistoryOrThrow(patternId) {
|
|
382
|
+
const history = this.histories.get(patternId);
|
|
383
|
+
if (!history) {
|
|
384
|
+
throw new PatternHistoryNotFoundError(patternId);
|
|
385
|
+
}
|
|
386
|
+
return history;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Check if history exists for a pattern
|
|
390
|
+
*
|
|
391
|
+
* @param patternId - Pattern ID
|
|
392
|
+
* @returns True if history exists
|
|
393
|
+
*/
|
|
394
|
+
hasPatternHistory(patternId) {
|
|
395
|
+
return this.histories.has(patternId);
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Query history events with filtering and pagination
|
|
399
|
+
*
|
|
400
|
+
* @param query - Query options
|
|
401
|
+
* @returns Query result with matching events
|
|
402
|
+
*/
|
|
403
|
+
query(query = {}) {
|
|
404
|
+
const startTime = Date.now();
|
|
405
|
+
// Collect all events from relevant histories
|
|
406
|
+
let events = [];
|
|
407
|
+
// Filter by pattern ID(s)
|
|
408
|
+
if (query.patternId) {
|
|
409
|
+
const history = this.histories.get(query.patternId);
|
|
410
|
+
if (history) {
|
|
411
|
+
events = [...history.events];
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
else if (query.patternIds && query.patternIds.length > 0) {
|
|
415
|
+
for (const patternId of query.patternIds) {
|
|
416
|
+
const history = this.histories.get(patternId);
|
|
417
|
+
if (history) {
|
|
418
|
+
events.push(...history.events);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
// Get all events
|
|
424
|
+
for (const history of this.histories.values()) {
|
|
425
|
+
events.push(...history.events);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
// Apply filters
|
|
429
|
+
events = this.applyFilters(events, query);
|
|
430
|
+
// Sort by timestamp descending (most recent first)
|
|
431
|
+
events.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
432
|
+
// Get total before pagination
|
|
433
|
+
const total = events.length;
|
|
434
|
+
// Apply pagination
|
|
435
|
+
const offset = query.offset ?? 0;
|
|
436
|
+
const limit = query.limit ?? events.length;
|
|
437
|
+
const hasMore = offset + limit < total;
|
|
438
|
+
events = events.slice(offset, offset + limit);
|
|
439
|
+
return {
|
|
440
|
+
events,
|
|
441
|
+
total,
|
|
442
|
+
hasMore,
|
|
443
|
+
executionTime: Date.now() - startTime,
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Apply filters to events
|
|
448
|
+
*/
|
|
449
|
+
applyFilters(events, query) {
|
|
450
|
+
return events.filter((event) => {
|
|
451
|
+
// Filter by event type
|
|
452
|
+
if (query.eventType) {
|
|
453
|
+
const types = Array.isArray(query.eventType)
|
|
454
|
+
? query.eventType
|
|
455
|
+
: [query.eventType];
|
|
456
|
+
if (!types.includes(event.type)) {
|
|
457
|
+
return false;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
// Filter by category
|
|
461
|
+
if (query.category) {
|
|
462
|
+
const categories = Array.isArray(query.category)
|
|
463
|
+
? query.category
|
|
464
|
+
: [query.category];
|
|
465
|
+
const history = this.histories.get(event.patternId);
|
|
466
|
+
if (!history || !categories.includes(history.category)) {
|
|
467
|
+
return false;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
// Filter by user
|
|
471
|
+
if (query.user && event.user !== query.user) {
|
|
472
|
+
return false;
|
|
473
|
+
}
|
|
474
|
+
// Filter by date range
|
|
475
|
+
if (query.after) {
|
|
476
|
+
const eventTime = new Date(event.timestamp).getTime();
|
|
477
|
+
const afterTime = new Date(query.after).getTime();
|
|
478
|
+
if (eventTime < afterTime) {
|
|
479
|
+
return false;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
if (query.before) {
|
|
483
|
+
const eventTime = new Date(event.timestamp).getTime();
|
|
484
|
+
const beforeTime = new Date(query.before).getTime();
|
|
485
|
+
if (eventTime > beforeTime) {
|
|
486
|
+
return false;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
return true;
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
// ==========================================================================
|
|
493
|
+
// Convenience Query Methods
|
|
494
|
+
// ==========================================================================
|
|
495
|
+
/**
|
|
496
|
+
* Get all history events
|
|
497
|
+
*/
|
|
498
|
+
getAllEvents() {
|
|
499
|
+
return this.query().events;
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Get events by type
|
|
503
|
+
*/
|
|
504
|
+
getEventsByType(eventType) {
|
|
505
|
+
return this.query({ eventType }).events;
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Get events by category
|
|
509
|
+
*/
|
|
510
|
+
getEventsByCategory(category) {
|
|
511
|
+
return this.query({ category }).events;
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Get events in date range
|
|
515
|
+
*/
|
|
516
|
+
getEventsInDateRange(after, before) {
|
|
517
|
+
return this.query({ after, before }).events;
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Get recent events
|
|
521
|
+
*
|
|
522
|
+
* @param limit - Maximum number of events to return
|
|
523
|
+
*/
|
|
524
|
+
getRecentEvents(limit = 50) {
|
|
525
|
+
return this.query({ limit }).events;
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Get approval history
|
|
529
|
+
*/
|
|
530
|
+
getApprovalHistory() {
|
|
531
|
+
return this.getEventsByType('approved');
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Get events by user
|
|
535
|
+
*/
|
|
536
|
+
getEventsByUser(user) {
|
|
537
|
+
return this.query({ user }).events;
|
|
538
|
+
}
|
|
539
|
+
// ==========================================================================
|
|
540
|
+
// Pruning
|
|
541
|
+
// ==========================================================================
|
|
542
|
+
/**
|
|
543
|
+
* Prune history for a specific pattern
|
|
544
|
+
*
|
|
545
|
+
* Removes old entries based on maxEntriesPerPattern and maxAgeDays config.
|
|
546
|
+
*
|
|
547
|
+
* @param patternId - Pattern ID to prune
|
|
548
|
+
*/
|
|
549
|
+
prunePatternHistory(patternId) {
|
|
550
|
+
const history = this.histories.get(patternId);
|
|
551
|
+
if (!history) {
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
const now = Date.now();
|
|
555
|
+
const maxAgeMs = this.config.maxAgeDays * 24 * 60 * 60 * 1000;
|
|
556
|
+
// Filter out old events
|
|
557
|
+
history.events = history.events.filter((event) => {
|
|
558
|
+
const eventTime = new Date(event.timestamp).getTime();
|
|
559
|
+
return now - eventTime < maxAgeMs;
|
|
560
|
+
});
|
|
561
|
+
// Limit number of entries
|
|
562
|
+
if (history.events.length > this.config.maxEntriesPerPattern) {
|
|
563
|
+
// Keep most recent events
|
|
564
|
+
history.events = history.events
|
|
565
|
+
.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime())
|
|
566
|
+
.slice(0, this.config.maxEntriesPerPattern);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Prune all history
|
|
571
|
+
*
|
|
572
|
+
* Removes old entries based on maxEntriesPerPattern and maxAgeDays config.
|
|
573
|
+
*/
|
|
574
|
+
prune() {
|
|
575
|
+
for (const patternId of this.histories.keys()) {
|
|
576
|
+
this.prunePatternHistory(patternId);
|
|
577
|
+
}
|
|
578
|
+
// Remove empty histories
|
|
579
|
+
for (const [patternId, history] of this.histories.entries()) {
|
|
580
|
+
if (history.events.length === 0) {
|
|
581
|
+
this.histories.delete(patternId);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
this.dirty = true;
|
|
585
|
+
this.emitEvent('history:pruned', undefined, { count: this.histories.size });
|
|
586
|
+
this.scheduleAutoSave();
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Delete history for a specific pattern
|
|
590
|
+
*
|
|
591
|
+
* @param patternId - Pattern ID
|
|
592
|
+
* @returns True if history was deleted
|
|
593
|
+
*/
|
|
594
|
+
deletePatternHistory(patternId) {
|
|
595
|
+
const deleted = this.histories.delete(patternId);
|
|
596
|
+
if (deleted) {
|
|
597
|
+
this.dirty = true;
|
|
598
|
+
this.scheduleAutoSave();
|
|
599
|
+
}
|
|
600
|
+
return deleted;
|
|
601
|
+
}
|
|
602
|
+
// ==========================================================================
|
|
603
|
+
// Statistics
|
|
604
|
+
// ==========================================================================
|
|
605
|
+
/**
|
|
606
|
+
* Get statistics about the history store
|
|
607
|
+
*/
|
|
608
|
+
getStats() {
|
|
609
|
+
const eventsByType = {
|
|
610
|
+
created: 0,
|
|
611
|
+
approved: 0,
|
|
612
|
+
ignored: 0,
|
|
613
|
+
updated: 0,
|
|
614
|
+
deleted: 0,
|
|
615
|
+
confidence_changed: 0,
|
|
616
|
+
locations_changed: 0,
|
|
617
|
+
severity_changed: 0,
|
|
618
|
+
};
|
|
619
|
+
let totalEvents = 0;
|
|
620
|
+
let oldestEvent = null;
|
|
621
|
+
let newestEvent = null;
|
|
622
|
+
for (const history of this.histories.values()) {
|
|
623
|
+
for (const event of history.events) {
|
|
624
|
+
totalEvents++;
|
|
625
|
+
eventsByType[event.type]++;
|
|
626
|
+
if (!oldestEvent || event.timestamp < oldestEvent) {
|
|
627
|
+
oldestEvent = event.timestamp;
|
|
628
|
+
}
|
|
629
|
+
if (!newestEvent || event.timestamp > newestEvent) {
|
|
630
|
+
newestEvent = event.timestamp;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
return {
|
|
635
|
+
totalPatterns: this.histories.size,
|
|
636
|
+
totalEvents,
|
|
637
|
+
eventsByType,
|
|
638
|
+
oldestEvent,
|
|
639
|
+
newestEvent,
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
// ==========================================================================
|
|
643
|
+
// Event Handling
|
|
644
|
+
// ==========================================================================
|
|
645
|
+
/**
|
|
646
|
+
* Emit a history store event
|
|
647
|
+
*/
|
|
648
|
+
emitEvent(type, patternId, data) {
|
|
649
|
+
const event = {
|
|
650
|
+
type,
|
|
651
|
+
timestamp: new Date().toISOString(),
|
|
652
|
+
};
|
|
653
|
+
if (patternId !== undefined) {
|
|
654
|
+
event.patternId = patternId;
|
|
655
|
+
}
|
|
656
|
+
if (data !== undefined) {
|
|
657
|
+
event.data = data;
|
|
658
|
+
}
|
|
659
|
+
this.emit(type, event);
|
|
660
|
+
this.emit('*', event); // Wildcard for all events
|
|
661
|
+
}
|
|
662
|
+
// ==========================================================================
|
|
663
|
+
// Utility Methods
|
|
664
|
+
// ==========================================================================
|
|
665
|
+
/**
|
|
666
|
+
* Get the number of patterns with history
|
|
667
|
+
*/
|
|
668
|
+
get size() {
|
|
669
|
+
return this.histories.size;
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Check if the store has been loaded
|
|
673
|
+
*/
|
|
674
|
+
get isLoaded() {
|
|
675
|
+
return this.loaded;
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* Check if there are unsaved changes
|
|
679
|
+
*/
|
|
680
|
+
get isDirty() {
|
|
681
|
+
return this.dirty;
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Get the history directory path
|
|
685
|
+
*/
|
|
686
|
+
get path() {
|
|
687
|
+
return this.historyDir;
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Clear all history from memory (does not affect disk)
|
|
691
|
+
*/
|
|
692
|
+
clear() {
|
|
693
|
+
this.histories.clear();
|
|
694
|
+
this.dirty = true;
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* Dispose of the history store
|
|
698
|
+
*/
|
|
699
|
+
dispose() {
|
|
700
|
+
if (this.saveTimeout) {
|
|
701
|
+
clearTimeout(this.saveTimeout);
|
|
702
|
+
this.saveTimeout = null;
|
|
703
|
+
}
|
|
704
|
+
this.removeAllListeners();
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
//# sourceMappingURL=history-store.js.map
|