@retrivora-ai/rag-engine 1.8.4 → 1.8.6
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/LICENSE.txt +21 -0
- package/dist/{ILLMProvider-BOJFz3Na.d.mts → ILLMProvider-CbUtvWAW.d.mts} +2 -0
- package/dist/{ILLMProvider-BOJFz3Na.d.ts → ILLMProvider-CbUtvWAW.d.ts} +2 -0
- package/dist/handlers/index.d.mts +2 -2
- package/dist/handlers/index.d.ts +2 -2
- package/dist/handlers/index.js +100 -52
- package/dist/handlers/index.mjs +100 -52
- package/dist/{index-BwpcaziY.d.ts → index-DgzcnqZs.d.ts} +1 -1
- package/dist/{index-D3V9Et2M.d.mts → index-c_y_5qdw.d.mts} +1 -1
- package/dist/index.css +150 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +18 -4
- package/dist/index.mjs +18 -4
- package/dist/server.d.mts +3 -3
- package/dist/server.d.ts +3 -3
- package/dist/server.js +108 -55
- package/dist/server.mjs +108 -55
- package/package.json +1 -1
- package/src/app/globals.css +97 -0
- package/src/components/ChatWidget.tsx +23 -10
- package/src/config/RagConfig.ts +2 -0
- package/src/providers/vectordb/ChromaDBProvider.ts +4 -0
- package/src/providers/vectordb/MultiTablePostgresProvider.ts +18 -5
- package/src/providers/vectordb/PostgreSQLProvider.ts +14 -3
- package/src/providers/vectordb/QdrantProvider.ts +4 -1
- package/src/tailwind.css +100 -1
- package/src/utils/UITransformer.ts +99 -51
package/src/tailwind.css
CHANGED
|
@@ -1,2 +1,101 @@
|
|
|
1
1
|
@import "tailwindcss";
|
|
2
|
-
@source "./**/*.{ts,tsx,js,jsx}";
|
|
2
|
+
@source "./**/*.{ts,tsx,js,jsx}";
|
|
3
|
+
|
|
4
|
+
@theme {
|
|
5
|
+
--animate-shapeshift: shapeshift 5s ease-in-out infinite forwards;
|
|
6
|
+
--animate-gradientMove: gradientMove 3.5s ease-in-out infinite alternate;
|
|
7
|
+
|
|
8
|
+
@keyframes shapeshift {
|
|
9
|
+
0% { clip-path: var(--circle); rotate: 0turn; }
|
|
10
|
+
25% { clip-path: var(--flower); }
|
|
11
|
+
50% { clip-path: var(--cylinder); }
|
|
12
|
+
75% { clip-path: var(--hexagon); }
|
|
13
|
+
100% { clip-path: var(--circle); rotate: 1turn; }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@keyframes gradientMove {
|
|
17
|
+
0% { translate: 0 0; }
|
|
18
|
+
100% { translate: -75% -75%; }
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@layer base {
|
|
23
|
+
:root {
|
|
24
|
+
--star: shape(
|
|
25
|
+
evenodd from 50% 24.787%,
|
|
26
|
+
curve by 7.143% 18.016% with 0% 0% / 2.9725% 13.814%,
|
|
27
|
+
curve by 17.882% 7.197% with 4.171% 4.2025% / 17.882% 7.197%,
|
|
28
|
+
curve by -17.882% 8.6765% with 0% 0% / -13.711% 4.474%,
|
|
29
|
+
curve by -7.143% 16.5365% with -4.1705% 4.202% / -7.143% 16.5365%,
|
|
30
|
+
curve by -8.6115% -16.5365% with 0% 0% / -4.441% -12.3345%,
|
|
31
|
+
curve by -16.4135% -8.6765% with -4.171% -4.2025% / -16.4135% -8.6765%,
|
|
32
|
+
curve by 16.4135% -7.197% with 0% 0% / 12.2425% -2.9945%,
|
|
33
|
+
curve by 8.6115% -18.016% with 4.1705% -4.202% / 8.6115% -18.016%,
|
|
34
|
+
close
|
|
35
|
+
);
|
|
36
|
+
--flower: shape(
|
|
37
|
+
evenodd from 17.9665% 82.0335%,
|
|
38
|
+
curve by -12.349% -32.0335% with -13.239% -5.129% / -18.021% -15.402%,
|
|
39
|
+
curve by -0.0275% -22.203% with -3.1825% -9.331% / -3.074% -16.6605%,
|
|
40
|
+
curve by 12.3765% -9.8305% with 2.3835% -4.3365% / 6.565% -7.579%,
|
|
41
|
+
curve by 32.0335% -12.349% with 5.129% -13.239% / 15.402% -18.021%,
|
|
42
|
+
curve by 20.4535% -0.8665% with 8.3805% -2.858% / 15.1465% -3.062%,
|
|
43
|
+
curve by 11.58% 13.2155% with 5.225% 2.161% / 9.0355% 6.6475%,
|
|
44
|
+
curve by 12.349% 32.0335% with 13.239% 5.129% / 18.021% 15.402%,
|
|
45
|
+
curve by 0.5715% 21.1275% with 2.9805% 8.7395% / 3.0745% 15.723%,
|
|
46
|
+
curve by -12.9205% 10.906% with -2.26% 4.88% / -6.638% 8.472%,
|
|
47
|
+
curve by -32.0335% 12.349% with -5.129% 13.239% / -15.402% 18.021%,
|
|
48
|
+
curve by -21.1215% 0.5745% with -8.736% 2.9795% / -15.718% 3.0745%,
|
|
49
|
+
curve by -10.912% -12.9235% with -4.883% -2.2595% / -8.477% -6.6385%,
|
|
50
|
+
close
|
|
51
|
+
);
|
|
52
|
+
--hexagon: shape(
|
|
53
|
+
evenodd from 6.47% 67.001%,
|
|
54
|
+
curve by 0% -34.002% with -1.1735% -7.7% / -1.1735% -26.302%,
|
|
55
|
+
curve by 7.0415% -12.1965% with 0.7075% -4.641% / 3.3765% -9.2635%,
|
|
56
|
+
curve by 29.447% -17.001% with 6.0815% -4.8665% / 22.192% -14.1675%,
|
|
57
|
+
curve by 14.083% 0% with 4.3725% -1.708% / 9.7105% -1.708%,
|
|
58
|
+
curve by 29.447% 17.001% with 7.255% 2.8335% / 23.3655% 12.1345%,
|
|
59
|
+
curve by 7.0415% 12.1965% with 3.665% 2.933% / 6.334% 7.5555%,
|
|
60
|
+
curve by 0% 34.002% with 1.1735% 7.7% / 1.1735% 26.302%,
|
|
61
|
+
curve by -7.0415% 12.1965% with -0.7075% 4.641% / -3.3765% 9.2635%,
|
|
62
|
+
curve by -29.447% 17.001% with -6.0815% 4.8665% / -22.192% 14.1675%,
|
|
63
|
+
curve by -14.083% 0% with -4.3725% 1.708% / -9.7105% 1.708%,
|
|
64
|
+
curve by -29.447% -17.001% with -7.255% -2.8335% / -23.3655% -12.1345%,
|
|
65
|
+
curve by -7.0415% -12.1965% with -3.665% -2.933% / -6.334% -7.5555%,
|
|
66
|
+
close
|
|
67
|
+
);
|
|
68
|
+
--cylinder: shape(
|
|
69
|
+
evenodd from 10.5845% 59.7305%,
|
|
70
|
+
curve by 0% -19.461% with -0.113% -1.7525% / -0.11% -18.14%,
|
|
71
|
+
curve by 10.098% -26.213% with 0.837% -10.0375% / 3.821% -19.2625%,
|
|
72
|
+
curve by 29.3175% -13.0215% with 7.2175% -7.992% / 17.682% -13.0215%,
|
|
73
|
+
curve by 19.5845% 5.185% with 7.1265% 0% / 13.8135% 1.887%,
|
|
74
|
+
curve by 9.8595% 7.9775% with 3.7065% 2.1185% / 7.035% 4.8195%,
|
|
75
|
+
curve by 9.9715% 26.072% with 6.2015% 6.933% / 9.4345% 16.082%,
|
|
76
|
+
curve by 0% 19.461% with 0.074% 1.384% / 0.0745% 17.7715%,
|
|
77
|
+
curve by -13.0065% 29.1155% with -0.511% 11.5345% / -5.021% 21.933%,
|
|
78
|
+
curve by -26.409% 10.119% with -6.991% 6.288% / -16.254% 10.119%,
|
|
79
|
+
curve by -20.945% -5.9995% with -7.6935% 0% / -14.8755% -2.199%,
|
|
80
|
+
curve by -8.713% -7.404% with -3.255% -2.0385% / -6.1905% -4.537%,
|
|
81
|
+
curve by -9.7575% -25.831% with -6.074% -6.9035% / -9.1205% -15.963%,
|
|
82
|
+
close
|
|
83
|
+
);
|
|
84
|
+
--circle: shape(
|
|
85
|
+
evenodd from 13.482% 79.505%,
|
|
86
|
+
curve by -7.1945% -12.47% with -1.4985% -1.8575% / -6.328% -10.225%,
|
|
87
|
+
curve by 0.0985% -33.8965% with -4.1645% -10.7945% / -4.1685% -23.0235%,
|
|
88
|
+
curve by 6.9955% -12.101% with 1.72% -4.3825% / 4.0845% -8.458%,
|
|
89
|
+
curve by 30.125% -17.119% with 7.339% -9.1825% / 18.4775% -15.5135%,
|
|
90
|
+
curve by 13.4165% 0.095% with 4.432% -0.6105% / 8.9505% -0.5855%,
|
|
91
|
+
curve by 29.364% 16.9% with 11.6215% 1.77% / 22.102% 7.9015%,
|
|
92
|
+
curve by 7.176% 12.4145% with 3.002% 3.7195% / 5.453% 7.968%,
|
|
93
|
+
curve by -0.0475% 33.8925% with 4.168% 10.756% / 4.2305% 22.942%,
|
|
94
|
+
curve by -7.1135% 12.2825% with -1.74% 4.4535% / -4.1455% 8.592%,
|
|
95
|
+
curve by -29.404% 16.9075% with -7.202% 8.954% / -18.019% 15.137%,
|
|
96
|
+
curve by -14.19% -0.018% with -4.6635% 0.7255% / -9.4575% 0.7205%,
|
|
97
|
+
curve by -29.226% -16.8875% with -11.573% -1.8065% / -21.9955% -7.9235%,
|
|
98
|
+
close
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -29,7 +29,8 @@ export class UITransformer {
|
|
|
29
29
|
? retrievedData.filter(item => this.determineStockStatus(item))
|
|
30
30
|
: retrievedData;
|
|
31
31
|
|
|
32
|
-
const
|
|
32
|
+
const groupByField = this.determineGroupByField(userQuery, filteredData);
|
|
33
|
+
const categories = this.detectCategories(filteredData, groupByField);
|
|
33
34
|
const hasProducts = filteredData.some(item => this.isProductData(item));
|
|
34
35
|
const isTimeSeries = filteredData.some(item => this.isTimeSeriesData(item));
|
|
35
36
|
const isTrendQuery = this.isTrendQuery(userQuery);
|
|
@@ -39,14 +40,16 @@ export class UITransformer {
|
|
|
39
40
|
return this.transformToLineChart(filteredData);
|
|
40
41
|
}
|
|
41
42
|
|
|
43
|
+
const explicitChartRequest = this.isExplicitChartQuery(userQuery);
|
|
44
|
+
|
|
42
45
|
// 2. High Priority: Products (Always prefer carousel for products unless it's a specific breakdown request)
|
|
43
|
-
if (hasProducts && !this.shouldShowCategoryChart(userQuery
|
|
46
|
+
if (hasProducts && !explicitChartRequest && !this.shouldShowCategoryChart(userQuery)) {
|
|
44
47
|
return this.transformToProductCarousel(filteredData, config, trainedSchema);
|
|
45
48
|
}
|
|
46
49
|
|
|
47
50
|
// 3. Category breakdowns
|
|
48
|
-
if (categories.length > 1 && this.shouldShowCategoryChart(userQuery
|
|
49
|
-
return this.transformToPieChart(filteredData);
|
|
51
|
+
if (explicitChartRequest || (categories.length > 1 && this.shouldShowCategoryChart(userQuery))) {
|
|
52
|
+
return this.transformToPieChart(filteredData, groupByField);
|
|
50
53
|
}
|
|
51
54
|
|
|
52
55
|
// 4. Secondary: Products as carousel if not already handled
|
|
@@ -87,13 +90,16 @@ export class UITransformer {
|
|
|
87
90
|
* Transform data to pie chart format
|
|
88
91
|
*/
|
|
89
92
|
private static transformToPieChart(
|
|
90
|
-
data: VectorMatch[]
|
|
93
|
+
data: VectorMatch[],
|
|
94
|
+
groupByField: string
|
|
91
95
|
): UITransformationResponse {
|
|
92
|
-
|
|
93
|
-
|
|
96
|
+
let categories = this.detectCategories(data, groupByField);
|
|
97
|
+
if (categories.length === 0) categories = ['All Data'];
|
|
98
|
+
|
|
99
|
+
const categoryData = this.aggregateByCategory(data, categories, groupByField);
|
|
94
100
|
|
|
95
101
|
const pieData: PieChartData[] = Object.entries(categoryData).map(([label, count]) => {
|
|
96
|
-
const { inStockCount, outOfStockCount } = this.calculateStockCounts(label, data);
|
|
102
|
+
const { inStockCount, outOfStockCount } = this.calculateStockCounts(label, data, groupByField);
|
|
97
103
|
return {
|
|
98
104
|
label,
|
|
99
105
|
value: count as number,
|
|
@@ -104,7 +110,7 @@ export class UITransformer {
|
|
|
104
110
|
|
|
105
111
|
return {
|
|
106
112
|
type: 'pie_chart',
|
|
107
|
-
title:
|
|
113
|
+
title: `Distribution by ${groupByField.charAt(0).toUpperCase() + groupByField.slice(1)}`,
|
|
108
114
|
description: `Showing breakdown across ${categories.length} categories`,
|
|
109
115
|
data: pieData,
|
|
110
116
|
};
|
|
@@ -371,11 +377,16 @@ export class UITransformer {
|
|
|
371
377
|
return hasTimeKeyword || hasValidDateValue;
|
|
372
378
|
}
|
|
373
379
|
|
|
374
|
-
private static
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
380
|
+
private static isExplicitChartQuery(query: string): boolean {
|
|
381
|
+
const normalized = query.toLowerCase();
|
|
382
|
+
return normalized.includes('pie chart') ||
|
|
383
|
+
normalized.includes('piechart') ||
|
|
384
|
+
normalized.includes('bar chart') ||
|
|
385
|
+
normalized.includes('barchart') ||
|
|
386
|
+
normalized.includes('radar chart');
|
|
387
|
+
}
|
|
378
388
|
|
|
389
|
+
private static shouldShowCategoryChart(query: string): boolean {
|
|
379
390
|
const normalized = query.toLowerCase();
|
|
380
391
|
const chartKeywords = [
|
|
381
392
|
'distribution',
|
|
@@ -389,6 +400,10 @@ export class UITransformer {
|
|
|
389
400
|
'split',
|
|
390
401
|
'category breakdown',
|
|
391
402
|
'category distribution',
|
|
403
|
+
'pie chart',
|
|
404
|
+
'piechart',
|
|
405
|
+
'bar chart',
|
|
406
|
+
'barchart'
|
|
392
407
|
];
|
|
393
408
|
|
|
394
409
|
return chartKeywords.some(keyword => normalized.includes(keyword));
|
|
@@ -501,38 +516,57 @@ export class UITransformer {
|
|
|
501
516
|
}
|
|
502
517
|
|
|
503
518
|
/**
|
|
504
|
-
* Helper:
|
|
519
|
+
* Helper: Dynamically determine what field to group by based on user query
|
|
520
|
+
*/
|
|
521
|
+
private static determineGroupByField(query: string, data: VectorMatch[]): string {
|
|
522
|
+
const normalized = query.toLowerCase();
|
|
523
|
+
const match = normalized.match(/(?:by|across|per|based on)\s+([a-zA-Z]+)/i);
|
|
524
|
+
|
|
525
|
+
let targetField = 'category';
|
|
526
|
+
if (match && match[1]) {
|
|
527
|
+
let field = match[1].toLowerCase();
|
|
528
|
+
// Un-pluralize common words to match schema keys
|
|
529
|
+
if (field.endsWith('ies')) field = field.replace(/ies$/, 'y');
|
|
530
|
+
else if (field.endsWith('s') && field !== 'status') field = field.slice(0, -1);
|
|
531
|
+
|
|
532
|
+
const stopWords = ['the', 'all', 'different', 'various', 'some', 'these', 'those'];
|
|
533
|
+
if (!stopWords.includes(field)) {
|
|
534
|
+
targetField = field;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Verify field exists, or fallback
|
|
539
|
+
const hasTargetField = data.some(d => d.metadata && d.metadata[targetField] !== undefined);
|
|
540
|
+
if (!hasTargetField && targetField === 'category') {
|
|
541
|
+
if (data.some(d => d.metadata && d.metadata['type'] !== undefined)) return 'type';
|
|
542
|
+
if (data.some(d => d.metadata && d.metadata['brand'] !== undefined)) return 'brand';
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
return targetField;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Helper: Detect grouping values dynamically
|
|
505
550
|
*/
|
|
506
|
-
private static detectCategories(data: VectorMatch[]): string[] {
|
|
551
|
+
private static detectCategories(data: VectorMatch[], groupByField: string): string[] {
|
|
507
552
|
const categories = new Set<string>();
|
|
508
553
|
|
|
509
554
|
data.forEach(item => {
|
|
510
555
|
const meta = item.metadata || {};
|
|
511
556
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
categories.add(String(
|
|
515
|
-
|
|
516
|
-
if (
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
const contentCategories = Array.from(new Set(
|
|
526
|
-
Array.from(item.content.matchAll(/^\s*([^:\n]+):\s*(?:•|\*|-)*/gm))
|
|
527
|
-
.map(match => match[1].trim())
|
|
528
|
-
.filter(Boolean)
|
|
529
|
-
));
|
|
530
|
-
contentCategories.forEach((category) => categories.add(category));
|
|
531
|
-
|
|
532
|
-
// Parse from structured category hints in content
|
|
533
|
-
const categoryMatch = item.content.match(/(?:Category|Type|Class):\s*([^\n]+)/i);
|
|
534
|
-
if (categoryMatch) {
|
|
535
|
-
categories.add(categoryMatch[1].trim());
|
|
557
|
+
if (meta[groupByField] !== undefined) {
|
|
558
|
+
const val = meta[groupByField];
|
|
559
|
+
if (Array.isArray(val)) val.forEach(v => categories.add(String(v)));
|
|
560
|
+
else categories.add(String(val));
|
|
561
|
+
} else if (groupByField === 'category') {
|
|
562
|
+
// Fallbacks for legacy 'category' extraction
|
|
563
|
+
if (meta.type) categories.add(String(meta.type));
|
|
564
|
+
if (meta.tag) {
|
|
565
|
+
const tags = Array.isArray(meta.tag) ? meta.tag : [meta.tag];
|
|
566
|
+
tags.forEach(t => categories.add(String(t)));
|
|
567
|
+
}
|
|
568
|
+
const categoryMatch = item.content.match(/(?:Category|Type|Class):\s*([^\n]+)/i);
|
|
569
|
+
if (categoryMatch) categories.add(categoryMatch[1].trim());
|
|
536
570
|
}
|
|
537
571
|
});
|
|
538
572
|
|
|
@@ -540,11 +574,12 @@ export class UITransformer {
|
|
|
540
574
|
}
|
|
541
575
|
|
|
542
576
|
/**
|
|
543
|
-
* Helper: Aggregate data
|
|
577
|
+
* Helper: Aggregate data dynamically
|
|
544
578
|
*/
|
|
545
579
|
private static aggregateByCategory(
|
|
546
580
|
data: VectorMatch[],
|
|
547
|
-
categories: string[]
|
|
581
|
+
categories: string[],
|
|
582
|
+
groupByField: string
|
|
548
583
|
): Record<string, number> {
|
|
549
584
|
const result: Record<string, number> = {};
|
|
550
585
|
|
|
@@ -554,7 +589,13 @@ export class UITransformer {
|
|
|
554
589
|
|
|
555
590
|
data.forEach(item => {
|
|
556
591
|
const meta = item.metadata || {};
|
|
557
|
-
|
|
592
|
+
let itemCategory = 'Other';
|
|
593
|
+
|
|
594
|
+
if (meta[groupByField] !== undefined) {
|
|
595
|
+
itemCategory = String(meta[groupByField]);
|
|
596
|
+
} else if (groupByField === 'category' && meta.type) {
|
|
597
|
+
itemCategory = String(meta.type);
|
|
598
|
+
}
|
|
558
599
|
|
|
559
600
|
if (Object.prototype.hasOwnProperty.call(result, itemCategory)) {
|
|
560
601
|
result[itemCategory]++;
|
|
@@ -615,15 +656,21 @@ export class UITransformer {
|
|
|
615
656
|
}
|
|
616
657
|
|
|
617
658
|
/**
|
|
618
|
-
* Helper: Calculate stock counts
|
|
659
|
+
* Helper: Calculate stock counts dynamically
|
|
619
660
|
*/
|
|
620
|
-
private static calculateStockCounts(category: string, data: VectorMatch[]): { inStockCount: number; outOfStockCount: number } {
|
|
661
|
+
private static calculateStockCounts(category: string, data: VectorMatch[], groupByField: string): { inStockCount: number; outOfStockCount: number } {
|
|
621
662
|
let inStock = 0;
|
|
622
663
|
let outOfStock = 0;
|
|
623
664
|
|
|
624
665
|
data.forEach(d => {
|
|
625
666
|
const meta = d.metadata || {};
|
|
626
|
-
|
|
667
|
+
let itemCategory = 'Other';
|
|
668
|
+
if (meta[groupByField] !== undefined) {
|
|
669
|
+
itemCategory = String(meta[groupByField]);
|
|
670
|
+
} else if (groupByField === 'category' && meta.type) {
|
|
671
|
+
itemCategory = String(meta.type);
|
|
672
|
+
}
|
|
673
|
+
|
|
627
674
|
if (itemCategory === category) {
|
|
628
675
|
if (this.determineStockStatus(d)) {
|
|
629
676
|
inStock++;
|
|
@@ -772,12 +819,13 @@ DATA SHAPE per type:
|
|
|
772
819
|
- text: { "content": "<prose answer>" }
|
|
773
820
|
|
|
774
821
|
DECISION RULES (follow strictly):
|
|
775
|
-
1.
|
|
776
|
-
2.
|
|
777
|
-
3.
|
|
778
|
-
4.
|
|
779
|
-
5.
|
|
780
|
-
6.
|
|
822
|
+
1. IF the user explicitly asks for a "pie chart", "bar chart", or "table", you MUST output that exact type. Do not deviate from their request.
|
|
823
|
+
2. bar_chart → comparing quantities across categories (e.g. sales by region, price by product). Use this when there is only ONE value per category.
|
|
824
|
+
3. line_chart → trends or changes over time (dates, months, years, sequential events)
|
|
825
|
+
4. pie_chart → proportional breakdown or percentage distribution
|
|
826
|
+
5. radar_chart → comparing 2 or more products across MULTIPLE attributes.
|
|
827
|
+
6. table → multi-field structured records where each item has ≥ 3 attributes
|
|
828
|
+
7. text → conversational or free-form answers where no chart adds value
|
|
781
829
|
|
|
782
830
|
IMPORTANT:
|
|
783
831
|
- Aggregate numeric values from the raw data — do not pass raw object arrays as data.
|