@burdenoff/microfe-bigconsole 2026.804.2 → 2026.805.1

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.
@@ -1 +1 @@
1
- {"version":3,"file":"DataSinkViewPage.js","names":[],"sources":["../../../src/bigconsole/pages/DataSinkViewPage.tsx"],"sourcesContent":["/**\n * DataSink View Page\n *\n * Page for viewing DataSink details, data preview, and actions.\n */\n\nimport { memo, useState, useEffect, useCallback } from 'react';\nimport { useParams } from 'react-router';\nimport { useAppNavigate } from '../contexts';\nimport { newWorkflowPath } from '../BigConsoleRoutes';\nimport { isNative, nativeConfirm, nativeImpact, nativeNotify } from '../../utils/nativeBridge';\nimport { formatDistanceToNow } from 'date-fns';\nimport { useDataSinkOperations } from '../hooks/useDataSinkOperations';\nimport { useDataSinkSubscription } from '../hooks/useDataSinkSubscription';\nimport { useDataSinkStore } from '../store/dataSinkStore';\nimport { DataSinkStatusBadge } from '../components/datasink/DataSinkStatusBadge';\nimport { DataSinkDataViewer } from '../components/datasink/DataSinkDataViewer';\nimport { DataSinkTableViewer } from '../components/datasink/DataSinkTableViewer';\nimport { LinkedWorkflowsPanel } from '../components/datasink/LinkedWorkflowsPanel';\nimport { LinkedParsersPanel } from '../components/datasink/LinkedParsersPanel';\nimport { WorkflowStatusPanel } from '../components/datasink/WorkflowStatusPanel';\nimport { ExportDataSinkDialog } from '../components/datasink/ExportDataSinkDialog';\nimport { ImportDataGuideDialog } from '../components/datasink/ImportDataGuideDialog';\n\n// fe-libs UI components\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Card, CardContent, CardHeader, CardTitle } from '@burdenoff/fe-libs/ui';\nimport { GlassCard, IllustratedEmptyState, PagePurpose, NextSteps } from '@burdenoff/fe-libs/ui';\n\n// Lucide icons\nimport {\n ArrowLeft,\n Edit,\n RefreshCw,\n Trash2,\n Database,\n Loader2,\n AlertCircle,\n Eye,\n EyeOff,\n Download,\n Upload,\n Grid3X3,\n Code,\n} from 'lucide-react';\n\n// ============================================================================\n// COMPONENT\n// ============================================================================\n\nexport const DataSinkViewPage = memo(function DataSinkViewPage() {\n const { dataSinkId } = useParams<{ dataSinkId: string; consoleId: string }>();\n const navigate = useAppNavigate();\n\n // Local state\n const [showData, setShowData] = useState(false);\n const [isRefreshing, setIsRefreshing] = useState(false);\n const [isDeleting, setIsDeleting] = useState(false);\n const [isExportDialogOpen, setIsExportDialogOpen] = useState(false);\n const [isImportDialogOpen, setIsImportDialogOpen] = useState(false);\n const [dataPreview, setDataPreview] = useState<Record<string, unknown> | null>(null);\n const [dataLoading, setDataLoading] = useState(false);\n const [dataViewMode, setDataViewMode] = useState<'table' | 'json'>('table');\n // v3.0: server-side pagination for array payloads (avoids loading millions of rows).\n const [pageSize, setPageSize] = useState(100);\n const [pageOffset, setPageOffset] = useState(0);\n const [dataTotalCount, setDataTotalCount] = useState<number | null>(null);\n const [dataHasMore, setDataHasMore] = useState(false);\n\n // Store\n const dataSink = useDataSinkStore((state) => (dataSinkId ? state.dataSinks.get(dataSinkId) : undefined));\n\n // Operations\n const { loading, error, fetchDataSink, getDataSinkData, refreshDataSink, deleteDataSink } = useDataSinkOperations({\n skipFetch: true,\n });\n\n // Subscribe to updates\n useDataSinkSubscription({\n dataSinkId,\n enabled: !!dataSinkId,\n });\n\n // Load DataSink on mount - always fetch full data to get workflowLinks\n useEffect(() => {\n if (dataSinkId) {\n fetchDataSink(dataSinkId);\n }\n }, [dataSinkId, fetchDataSink]);\n\n // Handle back navigation\n const handleBack = useCallback(() => {\n navigate(`/datasinks`);\n }, [navigate]);\n\n // Handle edit\n const handleEdit = useCallback(() => {\n navigate(`/datasinks/${dataSinkId}/edit`);\n }, [navigate, dataSinkId]);\n\n // Handle refresh\n const handleRefresh = useCallback(async () => {\n if (!dataSinkId) return;\n setIsRefreshing(true);\n try {\n await refreshDataSink({ id: dataSinkId });\n } catch (err) {\n } finally {\n setIsRefreshing(false);\n }\n }, [dataSinkId, refreshDataSink]);\n\n // Handle delete\n const handleDelete = useCallback(async () => {\n if (!dataSinkId) return;\n const native = await nativeConfirm({\n title: 'Delete DataSink?',\n message: 'Are you sure you want to delete this DataSink?',\n okButtonTitle: 'Delete',\n cancelButtonTitle: 'Cancel',\n });\n const ok = native === null ? window.confirm('Are you sure you want to delete this DataSink?') : native;\n if (!ok) return;\n nativeImpact('medium');\n setIsDeleting(true);\n try {\n await deleteDataSink(dataSinkId);\n if (isNative()) void nativeNotify('success');\n navigate(`/datasinks`);\n } catch (err) {\n if (isNative()) void nativeNotify('error');\n } finally {\n setIsDeleting(false);\n }\n }, [dataSinkId, deleteDataSink, navigate]);\n\n // Load one page of the sink's data (v3.0: server-side pagination).\n const loadDataPage = useCallback(\n async (offset: number, size: number) => {\n if (!dataSinkId) return;\n setDataLoading(true);\n try {\n const result = await getDataSinkData({ id: dataSinkId }, { limit: size, offset });\n if (result) {\n setDataPreview((result.raw as Record<string, unknown>) || {});\n // totalCount is non-null only for array payloads being paginated.\n setDataTotalCount(typeof result.totalCount === 'number' ? result.totalCount : null);\n setDataHasMore(Boolean(result.hasMore));\n setPageOffset(offset);\n }\n } catch {\n // handled by the hook's error state\n } finally {\n setDataLoading(false);\n }\n },\n [dataSinkId, getDataSinkData]\n );\n\n // Handle show data (toggles the preview; loads the first page).\n const handleShowData = useCallback(async () => {\n if (!dataSinkId) return;\n if (showData) {\n setShowData(false);\n return;\n }\n await loadDataPage(0, pageSize);\n setShowData(true);\n }, [dataSinkId, showData, loadDataPage, pageSize]);\n\n const handlePageSizeChange = useCallback(\n (size: number) => {\n setPageSize(size);\n void loadDataPage(0, size);\n },\n [loadDataPage]\n );\n\n // Handle export\n const handleExport = useCallback(() => {\n setIsExportDialogOpen(true);\n }, []);\n\n const handleExportClose = useCallback(() => {\n setIsExportDialogOpen(false);\n }, []);\n\n // Handle import — opens a guide explaining that data is imported via a workflow.\n const handleImport = useCallback(() => {\n setIsImportDialogOpen(true);\n }, []);\n\n const handleImportClose = useCallback(() => {\n setIsImportDialogOpen(false);\n }, []);\n\n const handleGoToWorkflows = useCallback(() => {\n setIsImportDialogOpen(false);\n // Navigate straight to the builder path (basePath-aware via useAppNavigate).\n // Going via /workflows/new would hit a <Navigate> redirect to an absolute\n // path that drops the MFE base path — see WorkflowsPage for the same pattern.\n navigate(newWorkflowPath());\n }, [navigate]);\n\n // Loading state\n if (loading && !dataSink) {\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <Loader2 className=\"w-8 h-8 text-text-secondary animate-spin\" />\n </div>\n );\n }\n\n // Error state\n if (error) {\n return (\n <div className=\"flex items-center justify-center min-h-[400px] p-card-padding\">\n <div className=\"text-center\">\n <AlertCircle className=\"w-12 h-12 text-status-error-text mx-auto mb-4\" />\n <p className=\"text-status-error-text text-body-sm font-medium mb-2\">Failed to load DataSink</p>\n <p className=\"text-text-tertiary text-body-xs mb-6\">{error.message}</p>\n <Button onClick={handleBack} variant=\"ghost\" size=\"sm\">\n <ArrowLeft className=\"w-4 h-4 mr-2\" />\n Go back\n </Button>\n </div>\n </div>\n );\n }\n\n // Not found state\n if (!dataSink) {\n return (\n <div className=\"flex items-center justify-center min-h-[400px] p-card-padding\">\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"DataSink not found\"\n description=\"The DataSink you're looking for doesn't exist or has been deleted.\"\n action={\n <Button onClick={handleBack} variant=\"default\" size=\"default\">\n <ArrowLeft className=\"w-4 h-4 mr-2\" />\n Go back to DataSinks\n </Button>\n }\n />\n </div>\n );\n }\n\n return (\n <div className=\"p-card-padding space-y-6\">\n {/* Header */}\n <div className=\"flex flex-col sm:flex-row sm:items-start sm:justify-between gap-4\">\n <div className=\"flex-1\">\n <Button onClick={handleBack} variant=\"ghost\" size=\"sm\" className=\"mb-4\">\n <ArrowLeft className=\"w-4 h-4 mr-2\" />\n Back to DataSinks\n </Button>\n <div className=\"flex items-center gap-2 flex-wrap\">\n <h1 className=\"text-heading-h1 font-bold text-text-primary\">{dataSink.name || 'Untitled'}</h1>\n <DataSinkStatusBadge status={dataSink.status} />\n </div>\n <p className=\"text-body-sm text-text-tertiary font-mono mt-1\">{dataSink.key}</p>\n {dataSink.description && <p className=\"text-body-sm text-text-secondary mt-2\">{dataSink.description}</p>}\n </div>\n\n {/* Actions */}\n <div className=\"flex items-center gap-2 flex-wrap\">\n <Button onClick={handleRefresh} disabled={isRefreshing} variant=\"secondary\" size=\"default\">\n <RefreshCw className={`w-4 h-4 mr-2 ${isRefreshing ? 'animate-spin' : ''}`} />\n {isRefreshing ? 'Refreshing...' : 'Refresh'}\n </Button>\n <Button onClick={handleImport} variant=\"secondary\" size=\"default\">\n <Upload className=\"w-4 h-4 mr-2\" />\n Import Data\n </Button>\n <Button onClick={handleExport} variant=\"secondary\" size=\"default\">\n <Download className=\"w-4 h-4 mr-2\" />\n Export\n </Button>\n <Button onClick={handleEdit} variant=\"secondary\" size=\"default\">\n <Edit className=\"w-4 h-4 mr-2\" />\n Edit\n </Button>\n <Button onClick={handleDelete} disabled={isDeleting} variant=\"destructive\" size=\"default\">\n <Trash2 className=\"w-4 h-4 mr-2\" />\n {isDeleting ? 'Deleting...' : 'Delete'}\n </Button>\n </div>\n </div>\n\n <PagePurpose>\n A data sink is a structured store that holds your processed data and serves it to dashboards. From here you can\n inspect its schema and stats, preview the cached rows, refresh it from its linked workflow, or export it.\n </PagePurpose>\n\n <NextSteps\n storageKey=\"bigconsole-datasink-view\"\n steps={[\n {\n id: 'preview',\n label: 'Preview the data',\n description: 'Open the cached rows to confirm the sink holds what you expect.',\n onClick: handleShowData,\n },\n {\n id: 'refresh',\n label: 'Refresh from source',\n description: 'Re-run the linked workflow to pull the latest data.',\n onClick: handleRefresh,\n },\n {\n id: 'build-dashboard',\n label: 'Visualize it on a dashboard',\n description: 'Add widgets that query this sink to see your data live.',\n onClick: () => navigate('/dashboards'),\n },\n {\n id: 'export',\n label: 'Export the data',\n description: 'Download the contents for use elsewhere.',\n onClick: handleExport,\n },\n ]}\n />\n\n {/* Details Grid */}\n <div className=\"grid gap-6 md:grid-cols-2\">\n {/* Info Panel — primary emphasis zone */}\n <GlassCard treatment=\"glass\" glow>\n <CardHeader>\n <CardTitle>Details</CardTitle>\n </CardHeader>\n <CardContent>\n <dl className=\"space-y-4 text-body-sm\">\n <div className=\"flex justify-between items-center\">\n <dt className=\"text-text-secondary\">Status</dt>\n <dd>\n <DataSinkStatusBadge status={dataSink.status} />\n </dd>\n </div>\n <div className=\"flex justify-between items-center\">\n <dt className=\"text-text-secondary\">Cache TTL</dt>\n <dd className=\"text-text-primary font-medium\">{dataSink.ttl ? `${dataSink.ttl}s` : 'None'}</dd>\n </div>\n <div className=\"flex justify-between items-center\">\n <dt className=\"text-text-secondary\">Refresh Type</dt>\n <dd className=\"text-text-primary font-medium capitalize\">{dataSink.refresh?.type || 'Manual'}</dd>\n </div>\n <div className=\"flex justify-between items-center\">\n <dt className=\"text-text-secondary\">Size</dt>\n <dd className=\"text-text-primary font-medium\">\n {dataSink.sizeBytes ? `${(dataSink.sizeBytes / 1024).toFixed(2)} KB` : '0 KB'}\n </dd>\n </div>\n <div className=\"flex justify-between items-center\">\n <dt className=\"text-text-secondary\">Version</dt>\n <dd className=\"text-text-primary font-medium\">{dataSink.version || 1}</dd>\n </div>\n <div className=\"flex justify-between items-center\">\n <dt className=\"text-text-secondary\">Last Refreshed</dt>\n <dd className=\"text-text-primary font-medium\">\n {dataSink.lastRefreshedAt\n ? formatDistanceToNow(new Date(dataSink.lastRefreshedAt), { addSuffix: true })\n : 'Never'}\n </dd>\n </div>\n <div className=\"flex justify-between items-center\">\n <dt className=\"text-text-secondary\">Created</dt>\n <dd className=\"text-text-primary font-medium\">\n {formatDistanceToNow(new Date(dataSink.createdAt), { addSuffix: true })}\n </dd>\n </div>\n </dl>\n </CardContent>\n </GlassCard>\n\n {/* Workflow Status Panel */}\n {dataSink.fluidGridsWorkflowId && (\n <WorkflowStatusPanel\n workflowId={dataSink.fluidGridsWorkflowId}\n executionId={dataSink.fluidGridsWorkflowExecutionId || undefined}\n lastRefreshedAt={dataSink.lastRefreshedAt || undefined}\n setupState={dataSink.setupState}\n status={dataSink.status}\n onRerunWorkflow={handleRefresh}\n disabled={isRefreshing}\n isRerunning={isRefreshing}\n />\n )}\n\n {/* Linked Workflows */}\n <LinkedWorkflowsPanel workflowLinks={dataSink.workflowLinks} />\n\n {/* Linked Parsers */}\n <LinkedParsersPanel dataSinkKey={dataSink.key} />\n </div>\n\n {/* Data Preview Section */}\n <Card>\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0\">\n <CardTitle className=\"flex items-center gap-2\">\n <Database className=\"w-4 h-4 text-text-secondary\" />\n Data Preview\n </CardTitle>\n <div className=\"flex items-center gap-2\">\n {showData && (\n <div className=\"flex items-center border border-border-default rounded-md overflow-hidden\">\n <button\n type=\"button\"\n onClick={() => setDataViewMode('table')}\n className={`p-1.5 ${dataViewMode === 'table' ? 'bg-bg-sunken text-text-primary' : 'text-text-secondary hover:text-text-primary'}`}\n title=\"Table view\"\n >\n <Grid3X3 className=\"w-4 h-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => setDataViewMode('json')}\n className={`p-1.5 ${dataViewMode === 'json' ? 'bg-bg-sunken text-text-primary' : 'text-text-secondary hover:text-text-primary'}`}\n title=\"JSON view\"\n >\n <Code className=\"w-4 h-4\" />\n </button>\n </div>\n )}\n <Button\n onClick={handleShowData}\n disabled={dataLoading}\n variant={showData ? 'secondary' : 'default'}\n size=\"sm\"\n >\n {dataLoading ? (\n <>\n <Loader2 className=\"w-4 h-4 mr-2 animate-spin\" />\n Loading...\n </>\n ) : showData ? (\n <>\n <EyeOff className=\"w-4 h-4 mr-2\" />\n Hide Data\n </>\n ) : (\n <>\n <Eye className=\"w-4 h-4 mr-2\" />\n Show Data\n </>\n )}\n </Button>\n </div>\n </CardHeader>\n\n {showData && dataPreview && (\n <CardContent>\n {dataViewMode === 'table' ? (\n <DataSinkTableViewer data={dataPreview} maxHeight=\"400px\" />\n ) : (\n <DataSinkDataViewer data={dataPreview} maxHeight=\"400px\" />\n )}\n {/* v3.0: pagination controls — shown only for array payloads. */}\n {dataTotalCount !== null && (\n <div className=\"flex items-center justify-between gap-4 mt-4 pt-4 border-t border-border-subtle\">\n <div className=\"flex items-center gap-2 text-body-sm text-text-secondary\">\n <span>Rows per page</span>\n <select\n aria-label=\"Rows per page\"\n className=\"bg-bg-canvas border border-border-default rounded px-2 py-0.5 text-text-primary\"\n value={pageSize}\n onChange={(e) => handlePageSizeChange(Number(e.target.value))}\n >\n {[100, 500, 1000].map((n) => (\n <option key={n} value={n}>\n {n}\n </option>\n ))}\n </select>\n </div>\n <div className=\"flex items-center gap-4 text-body-sm text-text-secondary\">\n <span>\n {dataTotalCount === 0\n ? '0 rows'\n : `${pageOffset + 1}–${Math.min(pageOffset + pageSize, dataTotalCount)} of ${dataTotalCount}`}\n </span>\n <Button\n variant=\"secondary\"\n size=\"sm\"\n disabled={pageOffset === 0 || dataLoading}\n onClick={() => void loadDataPage(Math.max(0, pageOffset - pageSize), pageSize)}\n >\n Previous\n </Button>\n <Button\n variant=\"secondary\"\n size=\"sm\"\n disabled={!dataHasMore || dataLoading}\n onClick={() => void loadDataPage(pageOffset + pageSize, pageSize)}\n >\n Next\n </Button>\n </div>\n </div>\n )}\n </CardContent>\n )}\n\n {!showData &&\n (dataSink.data && Object.keys(dataSink.data).length > 0 ? (\n <CardContent className=\"text-center p-8\">\n <Database className=\"w-10 h-10 text-text-tertiary mx-auto mb-4\" />\n <p className=\"text-body-sm text-text-secondary\">\n Click &quot;Show Data&quot; to preview the cached data.\n </p>\n </CardContent>\n ) : (\n // No data yet — surface the Import Data flow so a new user knows the next step.\n <CardContent className=\"text-center p-8\">\n <Database className=\"w-10 h-10 text-text-tertiary mx-auto mb-4\" />\n <p className=\"text-body-sm text-text-secondary mb-4\">\n This data sink has no data yet. Import data by building a workflow.\n </p>\n <Button onClick={handleImport} variant=\"default\" size=\"sm\">\n <Upload className=\"w-4 h-4 mr-2\" />\n Import Data\n </Button>\n </CardContent>\n ))}\n </Card>\n\n {/* Export DataSink Dialog */}\n <ExportDataSinkDialog\n isOpen={isExportDialogOpen}\n dataSinkId={dataSinkId || null}\n dataSinkName={dataSink.name || dataSink.key}\n onClose={handleExportClose}\n />\n\n {/* Import Data guide Dialog */}\n <ImportDataGuideDialog\n isOpen={isImportDialogOpen}\n dataSinkName={dataSink.name || dataSink.key}\n onClose={handleImportClose}\n onGoToWorkflows={handleGoToWorkflows}\n />\n </div>\n );\n});\n\nexport default DataSinkViewPage;\n"],"mappings":"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"}
1
+ {"version":3,"file":"DataSinkViewPage.js","names":[],"sources":["../../../src/bigconsole/pages/DataSinkViewPage.tsx"],"sourcesContent":["/**\n * DataSink View Page\n *\n * Page for viewing DataSink details, data preview, and actions.\n */\n\nimport { memo, useState, useEffect, useCallback } from 'react';\nimport { useParams } from 'react-router';\nimport { useAppNavigate } from '../contexts';\nimport { newWorkflowPath } from '../BigConsoleRoutes';\nimport { isNative, nativeConfirm, nativeImpact, nativeNotify } from '../../utils/nativeBridge';\nimport { formatDistanceToNow } from 'date-fns';\nimport { useDataSinkOperations } from '../hooks/useDataSinkOperations';\nimport { useDataSinkSubscription } from '../hooks/useDataSinkSubscription';\nimport { useDataSinkStore } from '../store/dataSinkStore';\nimport { DataSinkStatusBadge } from '../components/datasink/DataSinkStatusBadge';\nimport { DataSinkDataViewer } from '../components/datasink/DataSinkDataViewer';\nimport { DataSinkTableViewer } from '../components/datasink/DataSinkTableViewer';\nimport { LinkedWorkflowsPanel } from '../components/datasink/LinkedWorkflowsPanel';\nimport { LinkedParsersPanel } from '../components/datasink/LinkedParsersPanel';\nimport { WorkflowStatusPanel } from '../components/datasink/WorkflowStatusPanel';\nimport { ExportDataSinkDialog } from '../components/datasink/ExportDataSinkDialog';\nimport { ImportDataGuideDialog } from '../components/datasink/ImportDataGuideDialog';\n\n// fe-libs UI components\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Card, CardContent, CardHeader, CardTitle } from '@burdenoff/fe-libs/ui';\nimport { GlassCard, IllustratedEmptyState, PagePurpose, NextSteps } from '@burdenoff/fe-libs/ui';\n\n// Lucide icons\nimport {\n ArrowLeft,\n Edit,\n RefreshCw,\n Trash2,\n Database,\n Loader2,\n AlertCircle,\n Eye,\n EyeOff,\n Download,\n Upload,\n Grid3X3,\n Code,\n} from 'lucide-react';\n\n// ============================================================================\n// COMPONENT\n// ============================================================================\n\nexport const DataSinkViewPage = memo(function DataSinkViewPage() {\n const { dataSinkId } = useParams<{ dataSinkId: string; consoleId: string }>();\n const navigate = useAppNavigate();\n\n // Local state\n const [showData, setShowData] = useState(false);\n const [isRefreshing, setIsRefreshing] = useState(false);\n const [isDeleting, setIsDeleting] = useState(false);\n const [isExportDialogOpen, setIsExportDialogOpen] = useState(false);\n const [isImportDialogOpen, setIsImportDialogOpen] = useState(false);\n const [dataPreview, setDataPreview] = useState<Record<string, unknown> | null>(null);\n const [dataLoading, setDataLoading] = useState(false);\n const [dataViewMode, setDataViewMode] = useState<'table' | 'json'>('table');\n // v3.0: server-side pagination for array payloads (avoids loading millions of rows).\n const [pageSize, setPageSize] = useState(100);\n const [pageOffset, setPageOffset] = useState(0);\n const [dataTotalCount, setDataTotalCount] = useState<number | null>(null);\n const [dataHasMore, setDataHasMore] = useState(false);\n\n // Store\n const dataSink = useDataSinkStore((state) => (dataSinkId ? state.dataSinks.get(dataSinkId) : undefined));\n\n // Operations\n const { loading, error, fetchDataSink, getDataSinkData, refreshDataSink, updateDataSink, deleteDataSink } =\n useDataSinkOperations({\n skipFetch: true,\n });\n\n // Subscribe to updates\n useDataSinkSubscription({\n dataSinkId,\n enabled: !!dataSinkId,\n });\n\n // Load DataSink on mount - always fetch full data to get workflowLinks\n useEffect(() => {\n if (dataSinkId) {\n fetchDataSink(dataSinkId);\n }\n }, [dataSinkId, fetchDataSink]);\n\n // Handle back navigation\n const handleBack = useCallback(() => {\n navigate(`/datasinks`);\n }, [navigate]);\n\n // Handle edit\n const handleEdit = useCallback(() => {\n navigate(`/datasinks/${dataSinkId}/edit`);\n }, [navigate, dataSinkId]);\n\n // Handle refresh\n const handleRefresh = useCallback(async () => {\n if (!dataSinkId) return;\n setIsRefreshing(true);\n try {\n await refreshDataSink({ id: dataSinkId });\n } catch (err) {\n } finally {\n setIsRefreshing(false);\n }\n }, [dataSinkId, refreshDataSink]);\n\n // Handle delete\n const handleDelete = useCallback(async () => {\n if (!dataSinkId) return;\n const native = await nativeConfirm({\n title: 'Delete DataSink?',\n message: 'Are you sure you want to delete this DataSink?',\n okButtonTitle: 'Delete',\n cancelButtonTitle: 'Cancel',\n });\n const ok = native === null ? window.confirm('Are you sure you want to delete this DataSink?') : native;\n if (!ok) return;\n nativeImpact('medium');\n setIsDeleting(true);\n try {\n await deleteDataSink(dataSinkId);\n if (isNative()) void nativeNotify('success');\n navigate(`/datasinks`);\n } catch (err) {\n if (isNative()) void nativeNotify('error');\n } finally {\n setIsDeleting(false);\n }\n }, [dataSinkId, deleteDataSink, navigate]);\n\n // Load one page of the sink's data (v3.0: server-side pagination).\n const loadDataPage = useCallback(\n async (offset: number, size: number) => {\n if (!dataSinkId) return;\n setDataLoading(true);\n try {\n const result = await getDataSinkData({ id: dataSinkId }, { limit: size, offset });\n if (result) {\n setDataPreview((result.raw as Record<string, unknown>) || {});\n // totalCount is non-null only for array payloads being paginated.\n setDataTotalCount(typeof result.totalCount === 'number' ? result.totalCount : null);\n setDataHasMore(Boolean(result.hasMore));\n setPageOffset(offset);\n }\n } catch {\n // handled by the hook's error state\n } finally {\n setDataLoading(false);\n }\n },\n [dataSinkId, getDataSinkData]\n );\n\n // Handle show data (toggles the preview; loads the first page).\n const handleShowData = useCallback(async () => {\n if (!dataSinkId) return;\n if (showData) {\n setShowData(false);\n return;\n }\n await loadDataPage(0, pageSize);\n setShowData(true);\n }, [dataSinkId, showData, loadDataPage, pageSize]);\n\n const handlePageSizeChange = useCallback(\n (size: number) => {\n setPageSize(size);\n void loadDataPage(0, size);\n },\n [loadDataPage]\n );\n\n // Handle export\n const handleExport = useCallback(() => {\n setIsExportDialogOpen(true);\n }, []);\n\n const handleExportClose = useCallback(() => {\n setIsExportDialogOpen(false);\n }, []);\n\n // Handle import — opens a guide explaining that data is imported via a workflow.\n const handleImport = useCallback(() => {\n setIsImportDialogOpen(true);\n }, []);\n\n const handleImportClose = useCallback(() => {\n setIsImportDialogOpen(false);\n }, []);\n\n const handleGoToWorkflows = useCallback(() => {\n setIsImportDialogOpen(false);\n // Navigate straight to the builder path (basePath-aware via useAppNavigate).\n // Going via /workflows/new would hit a <Navigate> redirect to an absolute\n // path that drops the MFE base path — see WorkflowsPage for the same pattern.\n navigate(newWorkflowPath());\n }, [navigate]);\n\n // Direct CSV import — write the parsed rows straight to the sink (no workflow).\n const handleImportCsv = useCallback(\n async (rows: Record<string, unknown>[]): Promise<boolean> => {\n if (!dataSink) return false;\n // The DataSink `data` payload is a JSON scalar that accepts any JSON value;\n // tabular imports are stored as an array of row objects — the shape the\n // table viewer and parsers read. The generated input type narrows JSON to\n // Record<string, unknown>, so cast the row array to satisfy it.\n const updated = await updateDataSink({\n id: dataSink.id,\n version: dataSink.version,\n data: rows as unknown as Record<string, unknown>,\n });\n if (!updated) return false;\n // The import already succeeded. Revealing the data is best-effort — a\n // failure here must not turn a persisted import into a reported failure\n // (which would prompt a retry and hit a version conflict).\n try {\n await loadDataPage(0, pageSize);\n setShowData(true);\n } catch {\n // Data is saved; the user can open the preview manually.\n }\n return true;\n },\n [dataSink, updateDataSink, loadDataPage, pageSize]\n );\n\n // Loading state\n if (loading && !dataSink) {\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <Loader2 className=\"w-8 h-8 text-text-secondary animate-spin\" />\n </div>\n );\n }\n\n // Error state\n // Only treat the shared hook error as fatal when the sink never loaded. Once\n // it is loaded, transient action errors (e.g. a failed CSV import) surface\n // inline in their own UI and must not replace the whole page.\n if (error && !dataSink) {\n return (\n <div className=\"flex items-center justify-center min-h-[400px] p-card-padding\">\n <div className=\"text-center\">\n <AlertCircle className=\"w-12 h-12 text-status-error-text mx-auto mb-4\" />\n <p className=\"text-status-error-text text-body-sm font-medium mb-2\">Failed to load DataSink</p>\n <p className=\"text-text-tertiary text-body-xs mb-6\">{error.message}</p>\n <Button onClick={handleBack} variant=\"ghost\" size=\"sm\">\n <ArrowLeft className=\"w-4 h-4 mr-2\" />\n Go back\n </Button>\n </div>\n </div>\n );\n }\n\n // Not found state\n if (!dataSink) {\n return (\n <div className=\"flex items-center justify-center min-h-[400px] p-card-padding\">\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"DataSink not found\"\n description=\"The DataSink you're looking for doesn't exist or has been deleted.\"\n action={\n <Button onClick={handleBack} variant=\"default\" size=\"default\">\n <ArrowLeft className=\"w-4 h-4 mr-2\" />\n Go back to DataSinks\n </Button>\n }\n />\n </div>\n );\n }\n\n return (\n <div className=\"p-card-padding space-y-6\">\n {/* Header */}\n <div className=\"flex flex-col sm:flex-row sm:items-start sm:justify-between gap-4\">\n <div className=\"flex-1\">\n <Button onClick={handleBack} variant=\"ghost\" size=\"sm\" className=\"mb-4\">\n <ArrowLeft className=\"w-4 h-4 mr-2\" />\n Back to DataSinks\n </Button>\n <div className=\"flex items-center gap-2 flex-wrap\">\n <h1 className=\"text-heading-h1 font-bold text-text-primary\">{dataSink.name || 'Untitled'}</h1>\n <DataSinkStatusBadge status={dataSink.status} />\n </div>\n <p className=\"text-body-sm text-text-tertiary font-mono mt-1\">{dataSink.key}</p>\n {dataSink.description && <p className=\"text-body-sm text-text-secondary mt-2\">{dataSink.description}</p>}\n </div>\n\n {/* Actions */}\n <div className=\"flex items-center gap-2 flex-wrap\">\n <Button onClick={handleRefresh} disabled={isRefreshing} variant=\"secondary\" size=\"default\">\n <RefreshCw className={`w-4 h-4 mr-2 ${isRefreshing ? 'animate-spin' : ''}`} />\n {isRefreshing ? 'Refreshing...' : 'Refresh'}\n </Button>\n <Button onClick={handleImport} variant=\"secondary\" size=\"default\">\n <Upload className=\"w-4 h-4 mr-2\" />\n Import Data\n </Button>\n <Button onClick={handleExport} variant=\"secondary\" size=\"default\">\n <Download className=\"w-4 h-4 mr-2\" />\n Export\n </Button>\n <Button onClick={handleEdit} variant=\"secondary\" size=\"default\">\n <Edit className=\"w-4 h-4 mr-2\" />\n Edit\n </Button>\n <Button onClick={handleDelete} disabled={isDeleting} variant=\"destructive\" size=\"default\">\n <Trash2 className=\"w-4 h-4 mr-2\" />\n {isDeleting ? 'Deleting...' : 'Delete'}\n </Button>\n </div>\n </div>\n\n <PagePurpose>\n A data sink is a structured store that holds your processed data and serves it to dashboards. From here you can\n inspect its schema and stats, preview the cached rows, refresh it from its linked workflow, or export it.\n </PagePurpose>\n\n <NextSteps\n storageKey=\"bigconsole-datasink-view\"\n steps={[\n {\n id: 'preview',\n label: 'Preview the data',\n description: 'Open the cached rows to confirm the sink holds what you expect.',\n onClick: handleShowData,\n },\n {\n id: 'refresh',\n label: 'Refresh from source',\n description: 'Re-run the linked workflow to pull the latest data.',\n onClick: handleRefresh,\n },\n {\n id: 'build-dashboard',\n label: 'Visualize it on a dashboard',\n description: 'Add widgets that query this sink to see your data live.',\n onClick: () => navigate('/dashboards'),\n },\n {\n id: 'export',\n label: 'Export the data',\n description: 'Download the contents for use elsewhere.',\n onClick: handleExport,\n },\n ]}\n />\n\n {/* Details Grid */}\n <div className=\"grid gap-6 md:grid-cols-2\">\n {/* Info Panel — primary emphasis zone */}\n <GlassCard treatment=\"glass\" glow>\n <CardHeader>\n <CardTitle>Details</CardTitle>\n </CardHeader>\n <CardContent>\n <dl className=\"space-y-4 text-body-sm\">\n <div className=\"flex justify-between items-center\">\n <dt className=\"text-text-secondary\">Status</dt>\n <dd>\n <DataSinkStatusBadge status={dataSink.status} />\n </dd>\n </div>\n <div className=\"flex justify-between items-center\">\n <dt className=\"text-text-secondary\">Cache TTL</dt>\n <dd className=\"text-text-primary font-medium\">{dataSink.ttl ? `${dataSink.ttl}s` : 'None'}</dd>\n </div>\n <div className=\"flex justify-between items-center\">\n <dt className=\"text-text-secondary\">Refresh Type</dt>\n <dd className=\"text-text-primary font-medium capitalize\">{dataSink.refresh?.type || 'Manual'}</dd>\n </div>\n <div className=\"flex justify-between items-center\">\n <dt className=\"text-text-secondary\">Size</dt>\n <dd className=\"text-text-primary font-medium\">\n {dataSink.sizeBytes ? `${(dataSink.sizeBytes / 1024).toFixed(2)} KB` : '0 KB'}\n </dd>\n </div>\n <div className=\"flex justify-between items-center\">\n <dt className=\"text-text-secondary\">Version</dt>\n <dd className=\"text-text-primary font-medium\">{dataSink.version || 1}</dd>\n </div>\n <div className=\"flex justify-between items-center\">\n <dt className=\"text-text-secondary\">Last Refreshed</dt>\n <dd className=\"text-text-primary font-medium\">\n {dataSink.lastRefreshedAt\n ? formatDistanceToNow(new Date(dataSink.lastRefreshedAt), { addSuffix: true })\n : 'Never'}\n </dd>\n </div>\n <div className=\"flex justify-between items-center\">\n <dt className=\"text-text-secondary\">Created</dt>\n <dd className=\"text-text-primary font-medium\">\n {formatDistanceToNow(new Date(dataSink.createdAt), { addSuffix: true })}\n </dd>\n </div>\n </dl>\n </CardContent>\n </GlassCard>\n\n {/* Workflow Status Panel */}\n {dataSink.fluidGridsWorkflowId && (\n <WorkflowStatusPanel\n workflowId={dataSink.fluidGridsWorkflowId}\n executionId={dataSink.fluidGridsWorkflowExecutionId || undefined}\n lastRefreshedAt={dataSink.lastRefreshedAt || undefined}\n setupState={dataSink.setupState}\n status={dataSink.status}\n onRerunWorkflow={handleRefresh}\n disabled={isRefreshing}\n isRerunning={isRefreshing}\n />\n )}\n\n {/* Linked Workflows */}\n <LinkedWorkflowsPanel workflowLinks={dataSink.workflowLinks} />\n\n {/* Linked Parsers */}\n <LinkedParsersPanel dataSinkKey={dataSink.key} />\n </div>\n\n {/* Data Preview Section */}\n <Card>\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0\">\n <CardTitle className=\"flex items-center gap-2\">\n <Database className=\"w-4 h-4 text-text-secondary\" />\n Data Preview\n </CardTitle>\n <div className=\"flex items-center gap-2\">\n {showData && (\n <div className=\"flex items-center border border-border-default rounded-md overflow-hidden\">\n <button\n type=\"button\"\n onClick={() => setDataViewMode('table')}\n className={`p-1.5 ${dataViewMode === 'table' ? 'bg-bg-sunken text-text-primary' : 'text-text-secondary hover:text-text-primary'}`}\n title=\"Table view\"\n >\n <Grid3X3 className=\"w-4 h-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => setDataViewMode('json')}\n className={`p-1.5 ${dataViewMode === 'json' ? 'bg-bg-sunken text-text-primary' : 'text-text-secondary hover:text-text-primary'}`}\n title=\"JSON view\"\n >\n <Code className=\"w-4 h-4\" />\n </button>\n </div>\n )}\n <Button\n onClick={handleShowData}\n disabled={dataLoading}\n variant={showData ? 'secondary' : 'default'}\n size=\"sm\"\n >\n {dataLoading ? (\n <>\n <Loader2 className=\"w-4 h-4 mr-2 animate-spin\" />\n Loading...\n </>\n ) : showData ? (\n <>\n <EyeOff className=\"w-4 h-4 mr-2\" />\n Hide Data\n </>\n ) : (\n <>\n <Eye className=\"w-4 h-4 mr-2\" />\n Show Data\n </>\n )}\n </Button>\n </div>\n </CardHeader>\n\n {showData && dataPreview && (\n <CardContent>\n {dataViewMode === 'table' ? (\n <DataSinkTableViewer data={dataPreview} maxHeight=\"400px\" />\n ) : (\n <DataSinkDataViewer data={dataPreview} maxHeight=\"400px\" />\n )}\n {/* v3.0: pagination controls — shown only for array payloads. */}\n {dataTotalCount !== null && (\n <div className=\"flex items-center justify-between gap-4 mt-4 pt-4 border-t border-border-subtle\">\n <div className=\"flex items-center gap-2 text-body-sm text-text-secondary\">\n <span>Rows per page</span>\n <select\n aria-label=\"Rows per page\"\n className=\"bg-bg-canvas border border-border-default rounded px-2 py-0.5 text-text-primary\"\n value={pageSize}\n onChange={(e) => handlePageSizeChange(Number(e.target.value))}\n >\n {[100, 500, 1000].map((n) => (\n <option key={n} value={n}>\n {n}\n </option>\n ))}\n </select>\n </div>\n <div className=\"flex items-center gap-4 text-body-sm text-text-secondary\">\n <span>\n {dataTotalCount === 0\n ? '0 rows'\n : `${pageOffset + 1}–${Math.min(pageOffset + pageSize, dataTotalCount)} of ${dataTotalCount}`}\n </span>\n <Button\n variant=\"secondary\"\n size=\"sm\"\n disabled={pageOffset === 0 || dataLoading}\n onClick={() => void loadDataPage(Math.max(0, pageOffset - pageSize), pageSize)}\n >\n Previous\n </Button>\n <Button\n variant=\"secondary\"\n size=\"sm\"\n disabled={!dataHasMore || dataLoading}\n onClick={() => void loadDataPage(pageOffset + pageSize, pageSize)}\n >\n Next\n </Button>\n </div>\n </div>\n )}\n </CardContent>\n )}\n\n {!showData &&\n (dataSink.data && Object.keys(dataSink.data).length > 0 ? (\n <CardContent className=\"text-center p-8\">\n <Database className=\"w-10 h-10 text-text-tertiary mx-auto mb-4\" />\n <p className=\"text-body-sm text-text-secondary\">\n Click &quot;Show Data&quot; to preview the cached data.\n </p>\n </CardContent>\n ) : (\n // No data yet — surface the Import Data flow so a new user knows the next step.\n <CardContent className=\"text-center p-8\">\n <Database className=\"w-10 h-10 text-text-tertiary mx-auto mb-4\" />\n <p className=\"text-body-sm text-text-secondary mb-4\">\n This data sink has no data yet. Upload a CSV or build a workflow to import data.\n </p>\n <Button onClick={handleImport} variant=\"default\" size=\"sm\">\n <Upload className=\"w-4 h-4 mr-2\" />\n Import Data\n </Button>\n </CardContent>\n ))}\n </Card>\n\n {/* Export DataSink Dialog */}\n <ExportDataSinkDialog\n isOpen={isExportDialogOpen}\n dataSinkId={dataSinkId || null}\n dataSinkName={dataSink.name || dataSink.key}\n onClose={handleExportClose}\n />\n\n {/* Import Data guide Dialog */}\n <ImportDataGuideDialog\n isOpen={isImportDialogOpen}\n dataSinkName={dataSink.name || dataSink.key}\n hasExistingData={Boolean(dataSink.data && Object.keys(dataSink.data).length > 0)}\n onClose={handleImportClose}\n onGoToWorkflows={handleGoToWorkflows}\n onImportCsv={handleImportCsv}\n />\n </div>\n );\n});\n\nexport default DataSinkViewPage;\n"],"mappings":"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"}
@@ -0,0 +1,91 @@
1
+ //#region src/bigconsole/utils/csv.ts
2
+ var e = "CSV could not be parsed: an opening quote is never closed.";
3
+ function t(t, n) {
4
+ let r = [], i = "", a = [], o = !1, s = 0, c = t.length, l = () => {
5
+ a.push(i), i = "";
6
+ }, u = () => {
7
+ l(), r.push(a), a = [];
8
+ };
9
+ for (; s < c;) {
10
+ let e = t[s];
11
+ if (o) {
12
+ if (e === "\"") {
13
+ if (t[s + 1] === "\"") {
14
+ i += "\"", s += 2;
15
+ continue;
16
+ }
17
+ o = !1, s += 1;
18
+ continue;
19
+ }
20
+ i += e, s += 1;
21
+ continue;
22
+ }
23
+ if (e === "\"") {
24
+ i === "" ? o = !0 : i += e, s += 1;
25
+ continue;
26
+ }
27
+ if (e === n) {
28
+ l(), s += 1;
29
+ continue;
30
+ }
31
+ if (e === "\r") {
32
+ t[s + 1] === "\n" && (s += 1), u(), s += 1;
33
+ continue;
34
+ }
35
+ if (e === "\n") {
36
+ u(), s += 1;
37
+ continue;
38
+ }
39
+ i += e, s += 1;
40
+ }
41
+ if (o) throw Error(e);
42
+ return u(), r;
43
+ }
44
+ function n(e) {
45
+ return e.length <= 1 && (e[0] ?? "").trim() === "";
46
+ }
47
+ function r(e) {
48
+ let t = e.trim();
49
+ if (t === "") return null;
50
+ let n = t.toLowerCase();
51
+ if (n === "true") return !0;
52
+ if (n === "false") return !1;
53
+ if (/^-?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/.test(t) && !/^-?0\d/.test(t)) {
54
+ let e = Number(t);
55
+ if (Number.isFinite(e)) return Number.isInteger(e) && !Number.isSafeInteger(e) ? t : e;
56
+ }
57
+ return e;
58
+ }
59
+ function i(e) {
60
+ let t = /* @__PURE__ */ new Set(), n = /* @__PURE__ */ new Map();
61
+ return e.map((e, r) => {
62
+ let i = e.trim() || `column_${r + 1}`;
63
+ if (!t.has(i)) return t.add(i), i;
64
+ let a = n.get(i) ?? 0, o;
65
+ do
66
+ a += 1, o = `${i}_${a}`;
67
+ while (t.has(o));
68
+ return n.set(i, a), t.add(o), o;
69
+ });
70
+ }
71
+ function a(e, a = {}) {
72
+ let o = a.delimiter ?? ",", s = e.charCodeAt(0) === 65279 ? e.slice(1) : e;
73
+ if (s.trim() === "") throw Error("CSV is empty — nothing to import.");
74
+ let c = t(s, o).filter((e) => !n(e));
75
+ if (c.length === 0) throw Error("CSV is empty — nothing to import.");
76
+ let l = i(c[0]), u = c.slice(1);
77
+ if (u.length === 0) throw Error("CSV has a header row but no data rows to import.");
78
+ return {
79
+ columns: l,
80
+ rows: u.map((e) => {
81
+ let t = Object.create(null);
82
+ return l.forEach((n, i) => {
83
+ t[n] = r(e[i] ?? "");
84
+ }), t;
85
+ })
86
+ };
87
+ }
88
+ //#endregion
89
+ export { a as parseCsv };
90
+
91
+ //# sourceMappingURL=csv.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"csv.js","names":[],"sources":["../../../src/bigconsole/utils/csv.ts"],"sourcesContent":["/**\n * Dependency-free CSV parsing for direct DataSink imports.\n *\n * Parses RFC 4180-style CSV text (quoted fields, embedded delimiters/newlines,\n * escaped `\"\"` quotes, CRLF or LF line endings) into an array of row objects\n * keyed by the header row — the shape the DataSink table viewer and parsers\n * expect. Values are lightly type-inferred (numbers, booleans, empty → null).\n */\n\nexport type CsvValue = string | number | boolean | null;\n\nexport interface ParsedCsv {\n /** Column names taken from the header row, in order. */\n columns: string[];\n /** Data rows, each keyed by column name. */\n rows: Record<string, CsvValue>[];\n}\n\nexport interface ParseCsvOptions {\n /** Field delimiter. Defaults to \",\". */\n delimiter?: string;\n}\n\nconst UNTERMINATED_QUOTE_ERROR = 'CSV could not be parsed: an opening quote is never closed.';\n\n/**\n * Tokenize CSV text into a matrix of raw string fields, honoring quotes so that\n * delimiters and line breaks inside quoted fields are preserved.\n */\nfunction tokenize(text: string, delimiter: string): string[][] {\n const rows: string[][] = [];\n let field = '';\n let row: string[] = [];\n let inQuotes = false;\n let i = 0;\n const n = text.length;\n\n const endField = (): void => {\n row.push(field);\n field = '';\n };\n const endRow = (): void => {\n endField();\n rows.push(row);\n row = [];\n };\n\n while (i < n) {\n const char = text[i];\n\n if (inQuotes) {\n if (char === '\"') {\n if (text[i + 1] === '\"') {\n // Escaped quote inside a quoted field.\n field += '\"';\n i += 2;\n continue;\n }\n inQuotes = false;\n i += 1;\n continue;\n }\n field += char;\n i += 1;\n continue;\n }\n\n if (char === '\"') {\n // A quote only opens a quoted field at the very start of a field. A quote\n // in the middle of an unquoted field (e.g. `John \"JD\" Doe`) is treated as\n // a literal character rather than silently toggling quote mode and\n // swallowing surrounding text — a common shape in real-world exports.\n if (field === '') {\n inQuotes = true;\n } else {\n field += char;\n }\n i += 1;\n continue;\n }\n if (char === delimiter) {\n endField();\n i += 1;\n continue;\n }\n if (char === '\\r') {\n // Treat CRLF as a single line break.\n if (text[i + 1] === '\\n') i += 1;\n endRow();\n i += 1;\n continue;\n }\n if (char === '\\n') {\n endRow();\n i += 1;\n continue;\n }\n\n field += char;\n i += 1;\n }\n\n if (inQuotes) {\n throw new Error(UNTERMINATED_QUOTE_ERROR);\n }\n\n // Flush the trailing field/row for files that do not end in a newline.\n endRow();\n\n return rows;\n}\n\n/**\n * True only for a genuinely blank line — one with no delimiters and no content\n * (e.g. a trailing empty line). A row that bears delimiters represents a real\n * record of empty values and is kept.\n */\nfunction isBlankLine(row: string[]): boolean {\n return row.length <= 1 && (row[0] ?? '').trim() === '';\n}\n\n/** Light type inference so numeric/boolean CSV values are usable in widgets. */\nexport function inferCsvValue(raw: string): CsvValue {\n const trimmed = raw.trim();\n if (trimmed === '') return null;\n\n const lower = trimmed.toLowerCase();\n if (lower === 'true') return true;\n if (lower === 'false') return false;\n\n // Only coerce to a number when the whole token is a finite numeric literal.\n // Preserve leading-zero tokens (ZIP codes, SKUs, zero-padded IDs like \"00123\"\n // or \"007\") as strings — coercing them to numbers is lossy and destructive.\n if (/^-?(?:\\d+\\.?\\d*|\\.\\d+)(?:[eE][+-]?\\d+)?$/.test(trimmed) && !/^-?0\\d/.test(trimmed)) {\n const num = Number(trimmed);\n if (Number.isFinite(num)) {\n // Keep integer-valued literals beyond 2^53-1 as strings — coercing long\n // IDs / account numbers (e.g. 9007199254740993, or the same value exported\n // as 9007199254740993.0 / ...e0) would silently round them. Return the\n // trimmed token for consistency with the other coercion paths.\n if (Number.isInteger(num) && !Number.isSafeInteger(num)) return trimmed;\n return num;\n }\n }\n\n // Free-text values return the original cell verbatim (untrimmed): unlike\n // numeric/boolean tokens, surrounding whitespace in a string can be\n // meaningful data and is not ours to strip.\n return raw;\n}\n\n/** Ensure column names are non-empty and unique. */\nfunction normalizeColumns(header: string[]): string[] {\n const used = new Set<string>();\n const counters = new Map<string, number>();\n return header.map((raw, index) => {\n const base = raw.trim() || `column_${index + 1}`;\n if (!used.has(base)) {\n used.add(base);\n return base;\n }\n // Advance the suffix until the generated name is genuinely unique — a bare\n // `${base}_${n}` can collide with a literal header of the same text\n // (e.g. \"name\",\"name\",\"name_1\"), which would silently drop a column.\n let n = counters.get(base) ?? 0;\n let candidate: string;\n do {\n n += 1;\n candidate = `${base}_${n}`;\n } while (used.has(candidate));\n counters.set(base, n);\n used.add(candidate);\n return candidate;\n });\n}\n\n/**\n * Parse CSV text into columns + typed row objects.\n *\n * @throws Error when the input is empty or has a header but no data rows.\n */\nexport function parseCsv(text: string, options: ParseCsvOptions = {}): ParsedCsv {\n const delimiter = options.delimiter ?? ',';\n\n // Strip a UTF-8 BOM if present (common in spreadsheet exports).\n const clean = text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;\n\n if (clean.trim() === '') {\n throw new Error('CSV is empty — nothing to import.');\n }\n\n const matrix = tokenize(clean, delimiter).filter((row) => !isBlankLine(row));\n\n if (matrix.length === 0) {\n throw new Error('CSV is empty — nothing to import.');\n }\n\n const columns = normalizeColumns(matrix[0]);\n const dataRows = matrix.slice(1);\n\n if (dataRows.length === 0) {\n throw new Error('CSV has a header row but no data rows to import.');\n }\n\n const rows = dataRows.map((cells) => {\n // Null-prototype map so user-controlled header keys (e.g. \"__proto__\",\n // \"constructor\") become own properties instead of hitting Object.prototype\n // setters and silently dropping the column.\n const record = Object.create(null) as Record<string, CsvValue>;\n columns.forEach((col, index) => {\n record[col] = inferCsvValue(cells[index] ?? '');\n });\n return record;\n });\n\n return { columns, rows };\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-bigconsole",
3
- "version": "2026.804.2",
3
+ "version": "2026.805.1",
4
4
  "description": "BigConsole - AI-powered analytics and dashboard platform",
5
5
  "type": "module",
6
6
  "files": [
@@ -120,7 +120,7 @@
120
120
  "vite-plugin-dts": "3.9.1"
121
121
  },
122
122
  "publishConfig": {
123
- "access": "restricted"
123
+ "access": "public"
124
124
  },
125
125
  "overrides": {
126
126
  "lodash": ">=4.17.21",