@soulcraft/brainy 3.50.2 → 4.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.
Files changed (57) hide show
  1. package/CHANGELOG.md +201 -0
  2. package/README.md +358 -658
  3. package/dist/api/ConfigAPI.js +56 -19
  4. package/dist/api/DataAPI.js +24 -18
  5. package/dist/augmentations/storageAugmentations.d.ts +24 -0
  6. package/dist/augmentations/storageAugmentations.js +22 -0
  7. package/dist/brainy.js +32 -9
  8. package/dist/cli/commands/core.d.ts +20 -10
  9. package/dist/cli/commands/core.js +384 -82
  10. package/dist/cli/commands/import.d.ts +41 -0
  11. package/dist/cli/commands/import.js +456 -0
  12. package/dist/cli/commands/insights.d.ts +34 -0
  13. package/dist/cli/commands/insights.js +300 -0
  14. package/dist/cli/commands/neural.d.ts +6 -12
  15. package/dist/cli/commands/neural.js +113 -10
  16. package/dist/cli/commands/nlp.d.ts +28 -0
  17. package/dist/cli/commands/nlp.js +246 -0
  18. package/dist/cli/commands/storage.d.ts +64 -0
  19. package/dist/cli/commands/storage.js +730 -0
  20. package/dist/cli/index.js +210 -24
  21. package/dist/coreTypes.d.ts +206 -34
  22. package/dist/distributed/configManager.js +8 -6
  23. package/dist/distributed/shardMigration.js +2 -0
  24. package/dist/distributed/storageDiscovery.js +6 -4
  25. package/dist/embeddings/EmbeddingManager.d.ts +2 -2
  26. package/dist/embeddings/EmbeddingManager.js +5 -1
  27. package/dist/graph/lsm/LSMTree.js +32 -20
  28. package/dist/hnsw/typeAwareHNSWIndex.js +6 -2
  29. package/dist/storage/adapters/azureBlobStorage.d.ts +545 -0
  30. package/dist/storage/adapters/azureBlobStorage.js +1809 -0
  31. package/dist/storage/adapters/baseStorageAdapter.d.ts +16 -13
  32. package/dist/storage/adapters/fileSystemStorage.d.ts +21 -9
  33. package/dist/storage/adapters/fileSystemStorage.js +204 -127
  34. package/dist/storage/adapters/gcsStorage.d.ts +119 -9
  35. package/dist/storage/adapters/gcsStorage.js +317 -62
  36. package/dist/storage/adapters/memoryStorage.d.ts +30 -18
  37. package/dist/storage/adapters/memoryStorage.js +99 -94
  38. package/dist/storage/adapters/opfsStorage.d.ts +48 -10
  39. package/dist/storage/adapters/opfsStorage.js +201 -80
  40. package/dist/storage/adapters/r2Storage.d.ts +12 -5
  41. package/dist/storage/adapters/r2Storage.js +63 -15
  42. package/dist/storage/adapters/s3CompatibleStorage.d.ts +164 -17
  43. package/dist/storage/adapters/s3CompatibleStorage.js +472 -80
  44. package/dist/storage/adapters/typeAwareStorageAdapter.d.ts +38 -6
  45. package/dist/storage/adapters/typeAwareStorageAdapter.js +218 -39
  46. package/dist/storage/baseStorage.d.ts +41 -38
  47. package/dist/storage/baseStorage.js +110 -134
  48. package/dist/storage/storageFactory.d.ts +29 -2
  49. package/dist/storage/storageFactory.js +30 -1
  50. package/dist/utils/entityIdMapper.js +5 -2
  51. package/dist/utils/fieldTypeInference.js +8 -1
  52. package/dist/utils/metadataFilter.d.ts +3 -2
  53. package/dist/utils/metadataFilter.js +1 -0
  54. package/dist/utils/metadataIndex.js +2 -0
  55. package/dist/utils/metadataIndexChunking.js +9 -4
  56. package/dist/utils/periodicCleanup.js +1 -0
  57. package/package.json +3 -1
@@ -0,0 +1,300 @@
1
+ /**
2
+ * Insights & Analytics Commands
3
+ *
4
+ * Database insights, field statistics, and query optimization
5
+ */
6
+ import chalk from 'chalk';
7
+ import ora from 'ora';
8
+ import inquirer from 'inquirer';
9
+ import Table from 'cli-table3';
10
+ import { Brainy } from '../../brainy.js';
11
+ let brainyInstance = null;
12
+ const getBrainy = () => {
13
+ if (!brainyInstance) {
14
+ brainyInstance = new Brainy();
15
+ }
16
+ return brainyInstance;
17
+ };
18
+ const formatOutput = (data, options) => {
19
+ if (options.json) {
20
+ console.log(options.pretty ? JSON.stringify(data, null, 2) : JSON.stringify(data));
21
+ }
22
+ };
23
+ export const insightsCommands = {
24
+ /**
25
+ * Get comprehensive database insights
26
+ */
27
+ async insights(options) {
28
+ const spinner = ora('Analyzing database...').start();
29
+ try {
30
+ const brain = getBrainy();
31
+ // Get insights from Brainy
32
+ const insights = await brain.insights();
33
+ spinner.succeed('Analysis complete');
34
+ if (!options.json) {
35
+ console.log(chalk.cyan('\nšŸ“Š Database Insights:\n'));
36
+ // Overview - using actual insights return type
37
+ console.log(chalk.bold('Overview:'));
38
+ console.log(` Total Entities: ${chalk.yellow(insights.entities)}`);
39
+ console.log(` Total Relationships: ${chalk.yellow(insights.relationships)}`);
40
+ console.log(` Unique Types: ${chalk.yellow(Object.keys(insights.types).length)}`);
41
+ console.log(` Active Services: ${chalk.yellow(insights.services.join(', '))}`);
42
+ console.log(` Graph Density: ${chalk.yellow((insights.density * 100).toFixed(2))}%`);
43
+ // Entity types breakdown
44
+ const typeEntries = Object.entries(insights.types).sort((a, b) => b[1] - a[1]);
45
+ if (typeEntries.length > 0) {
46
+ console.log(chalk.bold('\nšŸ† Entities by Type:'));
47
+ const typeTable = new Table({
48
+ head: [chalk.cyan('Type'), chalk.cyan('Count'), chalk.cyan('Percentage')],
49
+ colWidths: [25, 12, 15]
50
+ });
51
+ typeEntries.slice(0, 10).forEach(([type, count]) => {
52
+ const percentage = insights.entities > 0 ? (count / insights.entities * 100) : 0;
53
+ typeTable.push([
54
+ type,
55
+ count.toString(),
56
+ `${percentage.toFixed(1)}%`
57
+ ]);
58
+ });
59
+ console.log(typeTable.toString());
60
+ if (typeEntries.length > 10) {
61
+ console.log(chalk.dim(`\n... and ${typeEntries.length - 10} more types`));
62
+ }
63
+ }
64
+ // Recommendations based on actual data
65
+ console.log(chalk.bold('\nšŸ’” Recommendations:'));
66
+ if (insights.entities === 0) {
67
+ console.log(` ${chalk.yellow('→')} Database is empty - add entities to get started`);
68
+ }
69
+ else {
70
+ if (insights.density < 0.1) {
71
+ console.log(` ${chalk.yellow('→')} Low graph density - consider adding more relationships`);
72
+ }
73
+ if (insights.relationships === 0) {
74
+ console.log(` ${chalk.yellow('→')} No relationships yet - use 'brainy relate' to connect entities`);
75
+ }
76
+ if (Object.keys(insights.types).length === 1) {
77
+ console.log(` ${chalk.yellow('→')} Only one entity type - consider adding diverse types for better organization`);
78
+ }
79
+ }
80
+ }
81
+ else {
82
+ formatOutput(insights, options);
83
+ }
84
+ }
85
+ catch (error) {
86
+ spinner.fail('Failed to get insights');
87
+ console.error(chalk.red('Insights failed:', error.message));
88
+ if (options.verbose) {
89
+ console.error(chalk.dim(error.stack));
90
+ }
91
+ process.exit(1);
92
+ }
93
+ },
94
+ /**
95
+ * Get available fields across all entities
96
+ */
97
+ async fields(options) {
98
+ const spinner = ora('Analyzing fields...').start();
99
+ try {
100
+ const brain = getBrainy();
101
+ // Get available fields from metadata index
102
+ const fields = await brain.getAvailableFields();
103
+ spinner.succeed(`Found ${fields.length} fields`);
104
+ if (!options.json) {
105
+ if (fields.length === 0) {
106
+ console.log(chalk.yellow('\nNo metadata fields found'));
107
+ console.log(chalk.dim('Add entities with metadata to see field statistics'));
108
+ }
109
+ else {
110
+ console.log(chalk.cyan(`\nšŸ“‹ Available Fields (${fields.length}):\n`));
111
+ // Get statistics for each field
112
+ const statistics = await brain.getFieldStatistics();
113
+ const table = new Table({
114
+ head: [chalk.cyan('Field'), chalk.cyan('Occurrences'), chalk.cyan('Unique Values')],
115
+ colWidths: [30, 15, 20]
116
+ });
117
+ for (const field of fields.slice(0, 50)) {
118
+ const stats = statistics.get(field);
119
+ table.push([
120
+ field,
121
+ stats?.count || 0,
122
+ stats?.uniqueValues || 0
123
+ ]);
124
+ }
125
+ console.log(table.toString());
126
+ if (fields.length > 50) {
127
+ console.log(chalk.dim(`\n... and ${fields.length - 50} more fields`));
128
+ }
129
+ console.log(chalk.dim('\nšŸ’” Use --json to see all fields'));
130
+ }
131
+ }
132
+ else {
133
+ const statistics = await brain.getFieldStatistics();
134
+ const fieldsWithStats = fields.map(field => ({
135
+ field,
136
+ ...Object.fromEntries(statistics.get(field) || [])
137
+ }));
138
+ formatOutput(fieldsWithStats, options);
139
+ }
140
+ }
141
+ catch (error) {
142
+ spinner.fail('Failed to get fields');
143
+ console.error(chalk.red('Fields analysis failed:', error.message));
144
+ if (options.verbose) {
145
+ console.error(chalk.dim(error.stack));
146
+ }
147
+ process.exit(1);
148
+ }
149
+ },
150
+ /**
151
+ * Get field values for a specific field
152
+ */
153
+ async fieldValues(field, options) {
154
+ let spinner = null;
155
+ try {
156
+ // Interactive mode if no field provided
157
+ if (!field) {
158
+ spinner = ora('Getting available fields...').start();
159
+ const brain = getBrainy();
160
+ const availableFields = await brain.getAvailableFields();
161
+ spinner.stop();
162
+ const answer = await inquirer.prompt([{
163
+ type: 'list',
164
+ name: 'field',
165
+ message: 'Select field:',
166
+ choices: availableFields.slice(0, 50),
167
+ pageSize: 15
168
+ }]);
169
+ field = answer.field;
170
+ }
171
+ spinner = ora(`Getting values for field: ${field}...`).start();
172
+ const brain = getBrainy();
173
+ const values = await brain.getFieldValues(field);
174
+ const limit = options.limit ? parseInt(options.limit) : 100;
175
+ spinner.succeed(`Found ${values.length} unique values`);
176
+ if (!options.json) {
177
+ console.log(chalk.cyan(`\nšŸ” Values for field "${chalk.bold(field)}":\n`));
178
+ if (values.length === 0) {
179
+ console.log(chalk.yellow('No values found for this field'));
180
+ }
181
+ else {
182
+ // Group by value and count
183
+ const valueCounts = values.reduce((acc, val) => {
184
+ acc[val] = (acc[val] || 0) + 1;
185
+ return acc;
186
+ }, {});
187
+ const sorted = Object.entries(valueCounts)
188
+ .sort((a, b) => b[1] - a[1])
189
+ .slice(0, limit);
190
+ const table = new Table({
191
+ head: [chalk.cyan('Value'), chalk.cyan('Count')],
192
+ colWidths: [50, 12]
193
+ });
194
+ sorted.forEach(([value, count]) => {
195
+ table.push([value, count.toString()]);
196
+ });
197
+ console.log(table.toString());
198
+ if (values.length > limit) {
199
+ console.log(chalk.dim(`\n... and ${values.length - limit} more values (use --limit to show more)`));
200
+ }
201
+ }
202
+ }
203
+ else {
204
+ formatOutput({ field, values, count: values.length }, options);
205
+ }
206
+ }
207
+ catch (error) {
208
+ if (spinner)
209
+ spinner.fail('Failed to get field values');
210
+ console.error(chalk.red('Field values failed:', error.message));
211
+ if (options.verbose) {
212
+ console.error(chalk.dim(error.stack));
213
+ }
214
+ process.exit(1);
215
+ }
216
+ },
217
+ /**
218
+ * Get optimal query plan for filters
219
+ */
220
+ async queryPlan(options) {
221
+ let spinner = null;
222
+ try {
223
+ let filters = {};
224
+ // Interactive mode if no filters provided
225
+ if (!options.filters) {
226
+ const answer = await inquirer.prompt([{
227
+ type: 'editor',
228
+ name: 'filters',
229
+ message: 'Enter filter JSON (e.g., {"status": "active", "priority": {"$gte": 5}}):',
230
+ validate: (input) => {
231
+ if (!input.trim())
232
+ return 'Filters cannot be empty';
233
+ try {
234
+ JSON.parse(input);
235
+ return true;
236
+ }
237
+ catch {
238
+ return 'Invalid JSON format';
239
+ }
240
+ }
241
+ }]);
242
+ filters = JSON.parse(answer.filters);
243
+ }
244
+ else {
245
+ try {
246
+ filters = JSON.parse(options.filters);
247
+ }
248
+ catch {
249
+ console.error(chalk.red('Invalid JSON in --filters'));
250
+ process.exit(1);
251
+ }
252
+ }
253
+ spinner = ora('Analyzing optimal query plan...').start();
254
+ const brain = getBrainy();
255
+ const plan = await brain.getOptimalQueryPlan(filters);
256
+ spinner.succeed('Query plan generated');
257
+ if (!options.json) {
258
+ console.log(chalk.cyan('\nšŸŽÆ Optimal Query Plan:\n'));
259
+ console.log(chalk.bold('Filters:'));
260
+ console.log(JSON.stringify(filters, null, 2));
261
+ console.log(chalk.bold('\nšŸ“Š Query Execution Plan:'));
262
+ console.log(` Strategy: ${chalk.yellow(plan.strategy)}`);
263
+ console.log(` Estimated Cost: ${chalk.yellow(plan.estimatedCost)}`);
264
+ if (plan.fieldOrder && plan.fieldOrder.length > 0) {
265
+ console.log(chalk.bold('\nšŸ” Field Processing Order (Optimized):'));
266
+ plan.fieldOrder.forEach((field, index) => {
267
+ console.log(` ${index + 1}. ${chalk.green(field)}`);
268
+ });
269
+ }
270
+ console.log(chalk.bold('\nšŸ’” Strategy Explanation:'));
271
+ if (plan.strategy === 'exact') {
272
+ console.log(` ${chalk.yellow('→')} Using exact-match indexing for fast lookups`);
273
+ }
274
+ else if (plan.strategy === 'range') {
275
+ console.log(` ${chalk.yellow('→')} Using range-based scanning for numeric/date filters`);
276
+ }
277
+ else if (plan.strategy === 'hybrid') {
278
+ console.log(` ${chalk.yellow('→')} Using hybrid approach combining multiple index types`);
279
+ }
280
+ console.log(chalk.bold('\n⚔ Performance Tips:'));
281
+ console.log(` ${chalk.yellow('→')} Lower estimated cost means faster queries`);
282
+ console.log(` ${chalk.yellow('→')} Fields are processed in optimal order`);
283
+ console.log(` ${chalk.yellow('→')} Consider adding indexes for frequently used fields`);
284
+ }
285
+ else {
286
+ formatOutput({ filters, plan }, options);
287
+ }
288
+ }
289
+ catch (error) {
290
+ if (spinner)
291
+ spinner.fail('Failed to generate query plan');
292
+ console.error(chalk.red('Query plan failed:', error.message));
293
+ if (options.verbose) {
294
+ console.error(chalk.dim(error.stack));
295
+ }
296
+ process.exit(1);
297
+ }
298
+ }
299
+ };
300
+ //# sourceMappingURL=insights.js.map
@@ -22,18 +22,12 @@ export declare const neuralCommand: {
22
22
  builder: (yargs: any) => any;
23
23
  handler: (argv: CommandArguments) => Promise<void>;
24
24
  };
25
- declare function handleSimilarCommand(neural: any, argv: CommandArguments): Promise<void>;
26
- declare function handleClustersCommand(neural: any, argv: CommandArguments): Promise<void>;
27
- declare function handleHierarchyCommand(neural: any, argv: CommandArguments): Promise<void>;
28
- declare function handleNeighborsCommand(neural: any, argv: CommandArguments): Promise<void>;
29
- declare function handleOutliersCommand(neural: any, argv: CommandArguments): Promise<void>;
30
- declare function handleVisualizeCommand(neural: any, argv: CommandArguments): Promise<void>;
31
25
  export declare const neuralCommands: {
32
- similar: typeof handleSimilarCommand;
33
- cluster: typeof handleClustersCommand;
34
- hierarchy: typeof handleHierarchyCommand;
35
- related: typeof handleNeighborsCommand;
36
- outliers: typeof handleOutliersCommand;
37
- visualize: typeof handleVisualizeCommand;
26
+ similar(a?: string, b?: string, options?: any): Promise<void>;
27
+ cluster(options?: any): Promise<void>;
28
+ hierarchy(id?: string, options?: any): Promise<void>;
29
+ related(id?: string, options?: any): Promise<void>;
30
+ outliers(options?: any): Promise<void>;
31
+ visualize(options?: any): Promise<void>;
38
32
  };
39
33
  export default neuralCommand;
@@ -205,13 +205,49 @@ async function handleSimilarCommand(neural, argv) {
205
205
  }
206
206
  }
207
207
  async function handleClustersCommand(neural, argv) {
208
- const spinner = ora('šŸŽÆ Finding semantic clusters...').start();
208
+ let spinner = null;
209
209
  try {
210
- const options = {
210
+ let options = {
211
211
  algorithm: argv.algorithm,
212
212
  threshold: argv.threshold,
213
213
  maxClusters: argv.limit
214
214
  };
215
+ // Interactive mode if no algorithm specified or using defaults
216
+ if (!argv.algorithm || argv.algorithm === 'hierarchical') {
217
+ const answers = await inquirer.prompt([
218
+ {
219
+ type: 'list',
220
+ name: 'algorithm',
221
+ message: 'Choose clustering algorithm:',
222
+ default: argv.algorithm || 'hierarchical',
223
+ choices: [
224
+ { name: '🌳 Hierarchical (Tree-based, best for natural grouping)', value: 'hierarchical' },
225
+ { name: 'šŸ“Š K-Means (Fixed number of clusters)', value: 'kmeans' },
226
+ { name: 'šŸŽÆ DBSCAN (Density-based, finds arbitrary shapes)', value: 'dbscan' }
227
+ ]
228
+ },
229
+ {
230
+ type: 'number',
231
+ name: 'maxClusters',
232
+ message: 'Maximum number of clusters:',
233
+ default: argv.limit || 5,
234
+ when: (answers) => answers.algorithm === 'kmeans'
235
+ },
236
+ {
237
+ type: 'number',
238
+ name: 'threshold',
239
+ message: 'Similarity threshold (0-1):',
240
+ default: argv.threshold || 0.7,
241
+ validate: (input) => (input >= 0 && input <= 1) || 'Must be between 0 and 1'
242
+ }
243
+ ]);
244
+ options = {
245
+ algorithm: answers.algorithm,
246
+ threshold: answers.threshold,
247
+ maxClusters: answers.maxClusters || options.maxClusters
248
+ };
249
+ }
250
+ const spinner = ora('šŸŽÆ Finding semantic clusters...').start();
215
251
  const clusters = await neural.clusters(argv.query || options);
216
252
  spinner.succeed(`āœ… Found ${clusters.length} clusters`);
217
253
  if (argv.format === 'json') {
@@ -242,7 +278,8 @@ async function handleClustersCommand(neural, argv) {
242
278
  }
243
279
  }
244
280
  catch (error) {
245
- spinner.fail('šŸ’„ Failed to find clusters');
281
+ if (spinner)
282
+ spinner.fail('šŸ’„ Failed to find clusters');
246
283
  throw error;
247
284
  }
248
285
  }
@@ -505,14 +542,80 @@ function showHelp() {
505
542
  console.log(' --explain, -e Include explanations');
506
543
  console.log('');
507
544
  }
545
+ // Commander-compatible wrappers
508
546
  export const neuralCommands = {
509
- similar: handleSimilarCommand,
510
- cluster: handleClustersCommand,
511
- hierarchy: handleHierarchyCommand,
512
- related: handleNeighborsCommand,
513
- // path: handlePathCommand, // Coming in v3.21.0 - requires graph traversal implementation
514
- outliers: handleOutliersCommand,
515
- visualize: handleVisualizeCommand
547
+ async similar(a, b, options) {
548
+ const brain = new Brainy();
549
+ const neural = brain.neural();
550
+ // Build argv-style object for handler
551
+ const argv = {
552
+ _: ['neural', 'similar', a || '', b || ''].filter(x => x),
553
+ id: a,
554
+ query: b,
555
+ explain: options?.explain,
556
+ ...options
557
+ };
558
+ await handleSimilarCommand(neural, argv);
559
+ },
560
+ async cluster(options) {
561
+ const brain = new Brainy();
562
+ const neural = brain.neural();
563
+ const argv = {
564
+ _: ['neural', 'cluster'],
565
+ algorithm: options?.algorithm || 'hierarchical',
566
+ threshold: options?.threshold ? parseFloat(options.threshold) : 0.7,
567
+ limit: options?.maxClusters ? parseInt(options.maxClusters) : 10,
568
+ query: options?.near,
569
+ ...options
570
+ };
571
+ await handleClustersCommand(neural, argv);
572
+ },
573
+ async hierarchy(id, options) {
574
+ const brain = new Brainy();
575
+ const neural = brain.neural();
576
+ const argv = {
577
+ _: ['neural', 'hierarchy', id || ''].filter(x => x),
578
+ id,
579
+ ...options
580
+ };
581
+ await handleHierarchyCommand(neural, argv);
582
+ },
583
+ async related(id, options) {
584
+ const brain = new Brainy();
585
+ const neural = brain.neural();
586
+ const argv = {
587
+ _: ['neural', 'related', id || ''].filter(x => x),
588
+ id,
589
+ limit: options?.limit ? parseInt(options.limit) : 10,
590
+ threshold: options?.radius ? parseFloat(options.radius) : 0.3,
591
+ ...options
592
+ };
593
+ await handleNeighborsCommand(neural, argv);
594
+ },
595
+ async outliers(options) {
596
+ const brain = new Brainy();
597
+ const neural = brain.neural();
598
+ const argv = {
599
+ _: ['neural', 'outliers'],
600
+ threshold: options?.threshold ? parseFloat(options.threshold) : 0.3,
601
+ explain: options?.explain,
602
+ ...options
603
+ };
604
+ await handleOutliersCommand(neural, argv);
605
+ },
606
+ async visualize(options) {
607
+ const brain = new Brainy();
608
+ const neural = brain.neural();
609
+ const argv = {
610
+ _: ['neural', 'visualize'],
611
+ format: options?.format || 'json',
612
+ dimensions: options?.dimensions ? parseInt(options.dimensions) : 2,
613
+ limit: options?.maxNodes ? parseInt(options.maxNodes) : 500,
614
+ output: options?.output,
615
+ ...options
616
+ };
617
+ await handleVisualizeCommand(neural, argv);
618
+ }
516
619
  };
517
620
  export default neuralCommand;
518
621
  //# sourceMappingURL=neural.js.map
@@ -0,0 +1,28 @@
1
+ /**
2
+ * NLP Commands - Natural Language Processing
3
+ *
4
+ * Extract entities, concepts, and insights from text using Brainy's neural NLP
5
+ */
6
+ interface NLPOptions {
7
+ verbose?: boolean;
8
+ json?: boolean;
9
+ pretty?: boolean;
10
+ quiet?: boolean;
11
+ }
12
+ export declare const nlpCommands: {
13
+ /**
14
+ * Extract entities from text
15
+ */
16
+ extract(text: string | undefined, options: NLPOptions): Promise<void>;
17
+ /**
18
+ * Extract concepts from text
19
+ */
20
+ extractConcepts(text: string | undefined, options: NLPOptions & {
21
+ threshold?: string;
22
+ }): Promise<void>;
23
+ /**
24
+ * Analyze text with full NLP pipeline
25
+ */
26
+ analyze(text: string | undefined, options: NLPOptions): Promise<void>;
27
+ };
28
+ export {};