@unrdf/dark-matter 5.0.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/README.md +81 -0
- package/package.json +59 -0
- package/src/dark-matter/critical-path.mjs +367 -0
- package/src/dark-matter/index-advisor.mjs +242 -0
- package/src/dark-matter/index.mjs +244 -0
- package/src/dark-matter/optimizer.mjs +426 -0
- package/src/dark-matter/performance-metrics.mjs +242 -0
- package/src/dark-matter/query-analyzer.mjs +442 -0
- package/src/dark-matter/query-optimizer.mjs +283 -0
- package/src/dark-matter-core.mjs +743 -0
- package/src/index.mjs +60 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Index Advisor - Recommend indexes based on query patterns
|
|
3
|
+
* @module @unrdf/dark-matter/index-advisor
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { analyzeSparqlQuery } from './query-analyzer.mjs';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {import('n3').Store} Store
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Index recommendation schema
|
|
15
|
+
*/
|
|
16
|
+
const IndexRecommendationSchema = z.object({
|
|
17
|
+
type: z.enum(['predicate', 'subject_predicate', 'object', 'composite']),
|
|
18
|
+
priority: z.enum(['low', 'medium', 'high', 'critical']),
|
|
19
|
+
estimatedBenefit: z.number().min(0).max(100),
|
|
20
|
+
reason: z.string(),
|
|
21
|
+
indexConfig: z.object({
|
|
22
|
+
fields: z.array(z.string()),
|
|
23
|
+
unique: z.boolean().optional(),
|
|
24
|
+
}),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Analyze index needs based on query log
|
|
29
|
+
* @param {Store} store - RDF store
|
|
30
|
+
* @param {Array<string>} queryLog - Array of executed queries
|
|
31
|
+
* @returns {Array<Object>} Index recommendations
|
|
32
|
+
*
|
|
33
|
+
* @throws {TypeError} If store or queryLog is invalid
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* const recommendations = analyzeIndexNeeds(store, [query1, query2]);
|
|
37
|
+
* recommendations.forEach(r => console.log(r.type, r.priority));
|
|
38
|
+
*/
|
|
39
|
+
export function analyzeIndexNeeds(store, queryLog) {
|
|
40
|
+
if (!store || typeof store.getQuads !== 'function') {
|
|
41
|
+
throw new TypeError('analyzeIndexNeeds: store must be a valid Store instance');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!Array.isArray(queryLog)) {
|
|
45
|
+
throw new TypeError('analyzeIndexNeeds: queryLog must be an array');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const recommendations = [];
|
|
49
|
+
const predicateFrequency = new Map();
|
|
50
|
+
const subjectPredicateFrequency = new Map();
|
|
51
|
+
|
|
52
|
+
// Analyze query patterns
|
|
53
|
+
for (const query of queryLog) {
|
|
54
|
+
if (typeof query !== 'string') {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const analysis = analyzeSparqlQuery(query);
|
|
59
|
+
|
|
60
|
+
for (const pattern of analysis.patterns) {
|
|
61
|
+
// Track predicate frequency
|
|
62
|
+
if (!pattern.predicate.startsWith('?')) {
|
|
63
|
+
const count = predicateFrequency.get(pattern.predicate) || 0;
|
|
64
|
+
predicateFrequency.set(pattern.predicate, count + 1);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Track subject+predicate combinations
|
|
68
|
+
if (!pattern.subject.startsWith('?') && !pattern.predicate.startsWith('?')) {
|
|
69
|
+
const key = `${pattern.subject}|${pattern.predicate}`;
|
|
70
|
+
const count = subjectPredicateFrequency.get(key) || 0;
|
|
71
|
+
subjectPredicateFrequency.set(key, count + 1);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Generate predicate index recommendations
|
|
77
|
+
for (const [predicate, frequency] of predicateFrequency.entries()) {
|
|
78
|
+
if (frequency >= 3) {
|
|
79
|
+
const priority = frequency >= 10 ? 'high' : frequency >= 5 ? 'medium' : 'low';
|
|
80
|
+
const estimatedBenefit = Math.min(frequency * 10, 100);
|
|
81
|
+
|
|
82
|
+
recommendations.push({
|
|
83
|
+
type: 'predicate',
|
|
84
|
+
priority,
|
|
85
|
+
estimatedBenefit,
|
|
86
|
+
reason: `Predicate ${predicate} queried ${frequency} times`,
|
|
87
|
+
indexConfig: {
|
|
88
|
+
fields: ['predicate'],
|
|
89
|
+
unique: false,
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Generate composite index recommendations
|
|
96
|
+
for (const [_key, frequency] of subjectPredicateFrequency.entries()) {
|
|
97
|
+
if (frequency >= 2) {
|
|
98
|
+
const priority = frequency >= 5 ? 'high' : 'medium';
|
|
99
|
+
const estimatedBenefit = Math.min(frequency * 15, 100);
|
|
100
|
+
|
|
101
|
+
recommendations.push({
|
|
102
|
+
type: 'subject_predicate',
|
|
103
|
+
priority,
|
|
104
|
+
estimatedBenefit,
|
|
105
|
+
reason: `Subject+Predicate combination queried ${frequency} times`,
|
|
106
|
+
indexConfig: {
|
|
107
|
+
fields: ['subject', 'predicate'],
|
|
108
|
+
unique: false,
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Sort by estimated benefit
|
|
115
|
+
recommendations.sort((a, b) => b.estimatedBenefit - a.estimatedBenefit);
|
|
116
|
+
|
|
117
|
+
return recommendations.map(r => IndexRecommendationSchema.parse(r));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Suggest index for specific pattern
|
|
122
|
+
* @param {Object} pattern - Triple pattern
|
|
123
|
+
* @returns {Object} Index suggestion
|
|
124
|
+
*
|
|
125
|
+
* @throws {TypeError} If pattern is invalid
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* const suggestion = suggestIndexForPattern({
|
|
129
|
+
* subject: '?s',
|
|
130
|
+
* predicate: '<http://xmlns.com/foaf/0.1/name>',
|
|
131
|
+
* object: '?name'
|
|
132
|
+
* });
|
|
133
|
+
*/
|
|
134
|
+
export function suggestIndexForPattern(pattern) {
|
|
135
|
+
if (!pattern || typeof pattern !== 'object') {
|
|
136
|
+
throw new TypeError('suggestIndexForPattern: pattern must be an object');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const { subject, predicate, object } = pattern;
|
|
140
|
+
|
|
141
|
+
if (!subject || !predicate || !object) {
|
|
142
|
+
throw new TypeError('suggestIndexForPattern: pattern must have subject, predicate, and object');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Specific predicate - recommend predicate index
|
|
146
|
+
if (!predicate.startsWith('?')) {
|
|
147
|
+
return {
|
|
148
|
+
type: 'predicate',
|
|
149
|
+
priority: 'high',
|
|
150
|
+
estimatedBenefit: 70,
|
|
151
|
+
reason: 'Specific predicate benefits from dedicated index',
|
|
152
|
+
indexConfig: {
|
|
153
|
+
fields: ['predicate'],
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Specific subject - recommend subject index
|
|
159
|
+
if (!subject.startsWith('?')) {
|
|
160
|
+
return {
|
|
161
|
+
type: 'subject_predicate',
|
|
162
|
+
priority: 'medium',
|
|
163
|
+
estimatedBenefit: 50,
|
|
164
|
+
reason: 'Specific subject can use subject-based index',
|
|
165
|
+
indexConfig: {
|
|
166
|
+
fields: ['subject'],
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Specific object - recommend object index
|
|
172
|
+
if (!object.startsWith('?')) {
|
|
173
|
+
return {
|
|
174
|
+
type: 'object',
|
|
175
|
+
priority: 'low',
|
|
176
|
+
estimatedBenefit: 30,
|
|
177
|
+
reason: 'Specific object may benefit from object index',
|
|
178
|
+
indexConfig: {
|
|
179
|
+
fields: ['object'],
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// All wildcards - no specific index recommended
|
|
185
|
+
return {
|
|
186
|
+
type: 'composite',
|
|
187
|
+
priority: 'low',
|
|
188
|
+
estimatedBenefit: 10,
|
|
189
|
+
reason: 'Pattern too general for specific index',
|
|
190
|
+
indexConfig: {
|
|
191
|
+
fields: ['subject', 'predicate', 'object'],
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Calculate index benefit for pattern
|
|
198
|
+
* @param {Object} pattern - Triple pattern
|
|
199
|
+
* @param {Object} indexConfig - Index configuration
|
|
200
|
+
* @returns {number} Benefit score 0-100
|
|
201
|
+
*
|
|
202
|
+
* @throws {TypeError} If parameters are invalid
|
|
203
|
+
*
|
|
204
|
+
* @example
|
|
205
|
+
* const benefit = calculateIndexBenefit(pattern, {
|
|
206
|
+
* fields: ['predicate'],
|
|
207
|
+
* unique: false
|
|
208
|
+
* });
|
|
209
|
+
*/
|
|
210
|
+
export function calculateIndexBenefit(pattern, indexConfig) {
|
|
211
|
+
if (!pattern || typeof pattern !== 'object') {
|
|
212
|
+
throw new TypeError('calculateIndexBenefit: pattern must be an object');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (!indexConfig || typeof indexConfig !== 'object') {
|
|
216
|
+
throw new TypeError('calculateIndexBenefit: indexConfig must be an object');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const { subject, predicate, object } = pattern;
|
|
220
|
+
const { fields } = indexConfig;
|
|
221
|
+
|
|
222
|
+
if (!Array.isArray(fields)) {
|
|
223
|
+
throw new TypeError('calculateIndexBenefit: indexConfig.fields must be an array');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
let benefit = 0;
|
|
227
|
+
|
|
228
|
+
// Check if indexed fields are bound (not variables)
|
|
229
|
+
for (const field of fields) {
|
|
230
|
+
if (field === 'subject' && !subject.startsWith('?')) {
|
|
231
|
+
benefit += 30;
|
|
232
|
+
}
|
|
233
|
+
if (field === 'predicate' && !predicate.startsWith('?')) {
|
|
234
|
+
benefit += 40;
|
|
235
|
+
}
|
|
236
|
+
if (field === 'object' && !object.startsWith('?')) {
|
|
237
|
+
benefit += 30;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return Math.min(benefit, 100);
|
|
242
|
+
}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Dark Matter 80/20 Query Optimization - Main Export
|
|
3
|
+
* @module dark-matter
|
|
4
|
+
*
|
|
5
|
+
* @description
|
|
6
|
+
* Main entry point for Dark Matter 80/20 query optimization system.
|
|
7
|
+
* Provides integrated query analysis, critical path identification,
|
|
8
|
+
* and query optimization following the 80/20 principle.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { QueryAnalyzer, createQueryAnalyzer } from './query-analyzer.mjs';
|
|
12
|
+
import { CriticalPathIdentifier, createCriticalPathIdentifier } from './critical-path.mjs';
|
|
13
|
+
import { DarkMatterOptimizer, createDarkMatterOptimizer } from './optimizer.mjs';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Integrated Dark Matter query optimization system
|
|
17
|
+
*/
|
|
18
|
+
export class DarkMatterQuerySystem {
|
|
19
|
+
/**
|
|
20
|
+
* Create a new Dark Matter query system
|
|
21
|
+
* @param {Object} [config] - Configuration
|
|
22
|
+
*/
|
|
23
|
+
constructor(config = {}) {
|
|
24
|
+
this.analyzer = createQueryAnalyzer(config.analyzer);
|
|
25
|
+
this.criticalPath = createCriticalPathIdentifier(config.criticalPath);
|
|
26
|
+
this.optimizer = createDarkMatterOptimizer(config.optimizer);
|
|
27
|
+
|
|
28
|
+
this.config = {
|
|
29
|
+
enableAutoOptimization: config.enableAutoOptimization !== false,
|
|
30
|
+
complexityThreshold: config.complexityThreshold || 100,
|
|
31
|
+
...config,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Analyze a query
|
|
37
|
+
* @param {string} query - SPARQL query
|
|
38
|
+
* @param {string} [queryId] - Optional query identifier
|
|
39
|
+
* @returns {Object} Analysis result
|
|
40
|
+
*/
|
|
41
|
+
analyze(query, queryId = null) {
|
|
42
|
+
return this.analyzer.analyze(query, queryId);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Log query execution for critical path analysis
|
|
47
|
+
* @param {string} queryId - Query identifier
|
|
48
|
+
* @param {string} query - SPARQL query
|
|
49
|
+
* @param {number} executionTime - Execution time in ms
|
|
50
|
+
* @param {Object} [metadata] - Optional metadata
|
|
51
|
+
*/
|
|
52
|
+
logExecution(queryId, query, executionTime, metadata = {}) {
|
|
53
|
+
this.criticalPath.logExecution(queryId, query, executionTime, metadata);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Identify critical queries
|
|
58
|
+
* @returns {Object} Critical path analysis
|
|
59
|
+
*/
|
|
60
|
+
identifyCriticalQueries() {
|
|
61
|
+
return this.criticalPath.identify();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Optimize a query
|
|
66
|
+
* @param {string} query - SPARQL query
|
|
67
|
+
* @param {Object} [analysis] - Optional pre-computed analysis
|
|
68
|
+
* @returns {Object} Optimization result
|
|
69
|
+
*/
|
|
70
|
+
optimize(query, analysis = null) {
|
|
71
|
+
// Analyze first if not provided
|
|
72
|
+
if (!analysis) {
|
|
73
|
+
analysis = this.analyzer.analyze(query);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Only optimize if above complexity threshold
|
|
77
|
+
if (analysis.complexity.score < this.config.complexityThreshold) {
|
|
78
|
+
return {
|
|
79
|
+
original: query,
|
|
80
|
+
optimized: query,
|
|
81
|
+
rules: [],
|
|
82
|
+
estimatedImprovement: {
|
|
83
|
+
before: analysis.complexity.score,
|
|
84
|
+
after: analysis.complexity.score,
|
|
85
|
+
percentageGain: 0,
|
|
86
|
+
},
|
|
87
|
+
timestamp: Date.now(),
|
|
88
|
+
skipped: true,
|
|
89
|
+
reason: 'Query complexity below threshold',
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return this.optimizer.optimize(query, analysis);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Analyze and optimize a query in one step
|
|
98
|
+
* @param {string} query - SPARQL query
|
|
99
|
+
* @param {string} [queryId] - Optional query identifier
|
|
100
|
+
* @returns {Object} Combined analysis and optimization
|
|
101
|
+
*/
|
|
102
|
+
analyzeAndOptimize(query, queryId = null) {
|
|
103
|
+
const analysis = this.analyze(query, queryId);
|
|
104
|
+
const optimization = this.optimize(query, analysis);
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
analysis,
|
|
108
|
+
optimization,
|
|
109
|
+
shouldOptimize: !optimization.skipped,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Process query execution: analyze, log, and optionally optimize
|
|
115
|
+
* @param {string} query - SPARQL query
|
|
116
|
+
* @param {number} executionTime - Execution time in ms
|
|
117
|
+
* @param {string} [queryId] - Optional query identifier
|
|
118
|
+
* @returns {Object} Processing result
|
|
119
|
+
*/
|
|
120
|
+
processExecution(query, executionTime, queryId = null) {
|
|
121
|
+
const analysis = this.analyze(query, queryId);
|
|
122
|
+
const id = queryId || analysis.queryId;
|
|
123
|
+
|
|
124
|
+
// Log execution
|
|
125
|
+
this.logExecution(id, query, executionTime, {
|
|
126
|
+
complexity: analysis.complexity.score,
|
|
127
|
+
expensiveOps: analysis.expensiveOperations.length,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Auto-optimize if enabled and above threshold
|
|
131
|
+
let optimization = null;
|
|
132
|
+
if (this.config.enableAutoOptimization) {
|
|
133
|
+
optimization = this.optimize(query, analysis);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
queryId: id,
|
|
138
|
+
analysis,
|
|
139
|
+
optimization,
|
|
140
|
+
logged: true,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Get comprehensive statistics
|
|
146
|
+
* @returns {Object} Statistics
|
|
147
|
+
*/
|
|
148
|
+
getStats() {
|
|
149
|
+
let criticalPathMetrics = null;
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
criticalPathMetrics = this.criticalPath.identify().metrics;
|
|
153
|
+
} catch (error) {
|
|
154
|
+
// Not enough data yet for critical path analysis
|
|
155
|
+
criticalPathMetrics = {
|
|
156
|
+
error: error.message,
|
|
157
|
+
totalQueries: 0,
|
|
158
|
+
criticalQueryCount: 0,
|
|
159
|
+
criticalQueryPercentage: 0,
|
|
160
|
+
totalExecutionTime: 0,
|
|
161
|
+
criticalExecutionTime: 0,
|
|
162
|
+
impactRatio: 0,
|
|
163
|
+
avgExecutionTime: 0,
|
|
164
|
+
p50: 0,
|
|
165
|
+
p90: 0,
|
|
166
|
+
p99: 0,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
analyzer: this.analyzer.getStats(),
|
|
172
|
+
criticalPath: criticalPathMetrics,
|
|
173
|
+
optimizer: this.optimizer.getStats(),
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Generate full report
|
|
179
|
+
* @returns {string} Markdown report
|
|
180
|
+
*/
|
|
181
|
+
getReport() {
|
|
182
|
+
let report = '# Dark Matter 80/20 Query Optimization Report\n\n';
|
|
183
|
+
|
|
184
|
+
// Analyzer stats
|
|
185
|
+
const analyzerStats = this.analyzer.getStats();
|
|
186
|
+
report += '## Query Analysis\n\n';
|
|
187
|
+
report += `- **Total Queries Analyzed**: ${analyzerStats.totalAnalyzed}\n`;
|
|
188
|
+
report += `- **Complex Queries**: ${analyzerStats.complexQueries}\n`;
|
|
189
|
+
report += `- **Simple Queries**: ${analyzerStats.simpleQueries}\n`;
|
|
190
|
+
report += `- **Complexity Ratio**: ${(analyzerStats.complexQueryRatio * 100).toFixed(1)}%\n`;
|
|
191
|
+
report += `- **Average Complexity**: ${analyzerStats.avgComplexity.toFixed(2)}\n\n`;
|
|
192
|
+
|
|
193
|
+
// Critical path
|
|
194
|
+
try {
|
|
195
|
+
const criticalPathReport = this.criticalPath.getReport();
|
|
196
|
+
report += criticalPathReport + '\n\n';
|
|
197
|
+
} catch (error) {
|
|
198
|
+
report += '## Critical Path Analysis\n\n';
|
|
199
|
+
report += `*Insufficient data for analysis: ${error.message}*\n\n`;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Optimizer stats
|
|
203
|
+
const optimizerStats = this.optimizer.getStats();
|
|
204
|
+
report += '## Optimization Statistics\n\n';
|
|
205
|
+
report += `- **Total Optimizations**: ${optimizerStats.totalOptimizations}\n`;
|
|
206
|
+
report += '- **Rules Applied**:\n';
|
|
207
|
+
|
|
208
|
+
for (const [rule, count] of Object.entries(optimizerStats.rulesApplied)) {
|
|
209
|
+
report += ` - ${rule}: ${count}\n`;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return report;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Clear all data
|
|
217
|
+
*/
|
|
218
|
+
clear() {
|
|
219
|
+
this.analyzer.resetStats();
|
|
220
|
+
this.criticalPath.clearLogs();
|
|
221
|
+
this.optimizer.resetStats();
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Create a Dark Matter query system
|
|
227
|
+
* @param {Object} [config] - Configuration
|
|
228
|
+
* @returns {DarkMatterQuerySystem} Query system
|
|
229
|
+
*/
|
|
230
|
+
export function createDarkMatterQuerySystem(config = {}) {
|
|
231
|
+
return new DarkMatterQuerySystem(config);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Re-export individual components
|
|
235
|
+
export {
|
|
236
|
+
QueryAnalyzer,
|
|
237
|
+
createQueryAnalyzer,
|
|
238
|
+
CriticalPathIdentifier,
|
|
239
|
+
createCriticalPathIdentifier,
|
|
240
|
+
DarkMatterOptimizer,
|
|
241
|
+
createDarkMatterOptimizer,
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
export default DarkMatterQuerySystem;
|