diff --git a/package.json b/package.json index d0d2adf..8a00aee 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "1.0.2", "private": true, "dependencies": { + "@duckdb/duckdb-wasm": "^1.33.1-dev45.0", "@emotion/cache": "11.14.0", "@emotion/react": "11.13.0", "@emotion/styled": "11.13.0", diff --git a/src/app/[locale]/gtfs-viewer/components/GtfsViewerClient.tsx b/src/app/[locale]/gtfs-viewer/components/GtfsViewerClient.tsx new file mode 100644 index 0000000..f8eea13 --- /dev/null +++ b/src/app/[locale]/gtfs-viewer/components/GtfsViewerClient.tsx @@ -0,0 +1,14 @@ +'use client'; + +// Client Component wrapper that lazy-loads GtfsViewerClient with ssr:false. +// This keeps DuckDB-WASM out of the server bundle and prevents the Turbopack +// WASM chunking crash at build time. ssr:false is allowed here because this +// is a Client Component. +import dynamic from 'next/dynamic'; + +const GtfsViewerClient = dynamic( + () => import('../../../components/gtfs-viewer/GtfsViewerClient'), + { ssr: false }, +); + +export default GtfsViewerClient; diff --git a/src/app/[locale]/gtfs-viewer/page.tsx b/src/app/[locale]/gtfs-viewer/page.tsx new file mode 100644 index 0000000..5984245 --- /dev/null +++ b/src/app/[locale]/gtfs-viewer/page.tsx @@ -0,0 +1,18 @@ +import { type ReactElement } from 'react'; +import { type Metadata } from 'next'; +import { routing } from '../../../i18n/routing'; +import { type Locale } from '../../../i18n/routing'; +import GtfsViewerClient from './components/GtfsViewerClient'; + +export const metadata: Metadata = { + title: 'GTFS Viewer POC | MobilityDatabase', + description: 'Explore GTFS dataset tables with efficient pagination and search via DuckDB-WASM.', +}; + +export function generateStaticParams(): Array<{ locale: Locale }> { + return routing.locales.map((locale) => ({ locale })); +} + +export default function GtfsViewerPage(): ReactElement { + return ; +} diff --git a/src/app/components/gtfs-viewer/GtfsViewerClient.tsx b/src/app/components/gtfs-viewer/GtfsViewerClient.tsx new file mode 100644 index 0000000..f3120b5 --- /dev/null +++ b/src/app/components/gtfs-viewer/GtfsViewerClient.tsx @@ -0,0 +1,695 @@ +'use client'; + +/** + * GTFS Viewer POC + * + * Loads Parquet files from a public GCS bucket (or local http.server) using + * DuckDB-WASM + HTTP Range requests. No backend required — all queries run + * in the browser. + * + * Workflow: + * 1. Paste a metadata.json URL → pick a table from the sidebar + * OR paste a direct .parquet URL → view it immediately + * 2. DuckDB fetches only the Parquet row groups that satisfy the search + * (same principle as PMTiles: index → byte-range fetch → render) + * 3. Pagination + per-column search work without loading the full file + */ + +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import { + Alert, + Box, + Button, + Chip, + CircularProgress, + Divider, + FormControl, + IconButton, + InputAdornment, + InputLabel, + MenuItem, + Paper, + Select, + Skeleton, + Stack, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TablePagination, + TableRow, + TableSortLabel, + TextField, + Tooltip, + Typography, +} from '@mui/material'; +import SearchIcon from '@mui/icons-material/Search'; +import ClearIcon from '@mui/icons-material/Clear'; +import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; +import OpenInNewIcon from '@mui/icons-material/OpenInNew'; + +// ─── Types ────────────────────────────────────────────────────────────────── + +interface TableMeta { + file: string; + row_count: number; + size_bytes: number; + columns: string[]; + sort_columns: string[]; + search_columns: string[]; +} + +interface GtfsMetadata { + source: string; + generated_at: string; + row_group_size: number; + tables: Record; +} + +type SortDir = 'asc' | 'desc'; + +interface GtfsViewerClientProps { + /** Pre-set URL (metadata.json or .parquet). Hides the URL input when provided. */ + initialUrl?: string; + /** When true, suppresses the page header and URL input — for embedding in feed pages. */ + embedded?: boolean; +} + +// ─── DuckDB initialisation (singleton, loaded once) ───────────────────────── + +let dbPromise: Promise | null = null; + +async function getDuckDB(): Promise { + if (!dbPromise) { + dbPromise = (async () => { + const duckdb = await import('@duckdb/duckdb-wasm'); + // Load WASM bundles from jsDelivr CDN — no webpack config needed + const BUNDLES = duckdb.getJsDelivrBundles(); + const bundle = await duckdb.selectBundle(BUNDLES); + + const workerUrl = URL.createObjectURL( + new Blob([`importScripts("${bundle.mainWorker!}");`], { type: 'text/javascript' }), + ); + const worker = new Worker(workerUrl); + const logger = new duckdb.ConsoleLogger(duckdb.LogLevel.WARNING); + const db = new duckdb.AsyncDuckDB(logger, worker); + await db.instantiate(bundle.mainModule, bundle.pthreadWorker); + URL.revokeObjectURL(workerUrl); + return db; + })(); + } + return dbPromise; +} + +// ─── Helpers ──────────────────────────────────────────────────────────────── + +function fmtRows(n: number): string { + if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`; + if (n >= 1_000) return `${(n / 1_000).toFixed(1)}k`; + return String(n); +} + +function fmtBytes(b: number): string { + if (b >= 1_000_000) return `${(b / 1_000_000).toFixed(1)} MB`; + if (b >= 1_000) return `${(b / 1_000).toFixed(0)} KB`; + return `${b} B`; +} + +function resolveParquetUrl(baseUrl: string, file: string): string { + const base = baseUrl.replace(/\/metadata\.json$/, ''); + return `${base}/${file}`; +} + +// Build a WHERE clause from search state. +// - searchColumn === '__searchable__': search only across the table's defined searchable columns +// - searchColumn === '__all__': search across every column (slow on large files) +// - anything else: search a specific single column +// When a specific column is selected and the term has no wildcards/spaces, uses exact `=` +// instead of ILIKE so DuckDB can skip row groups via Parquet min/max statistics. +function buildWhere( + allColumns: string[], + searchableColumns: string[], + searchTerm: string, + searchColumn: string, +): string { + if (!searchTerm.trim()) return ''; + const escaped = searchTerm.replace(/'/g, "''"); + const isExact = /^[^\s%*?]+$/.test(searchTerm); // no wildcards or spaces + + const ilike = (c: string) => `CAST("${c}" AS VARCHAR) ILIKE '%${escaped}%'`; + const exact = (c: string) => `CAST("${c}" AS VARCHAR) = '${escaped}'`; + + if (searchColumn === '__all__') { + return `WHERE (${allColumns.map(ilike).join(' OR ')})`; + } + if (searchColumn === '__searchable__') { + const cols = (searchableColumns.length > 0 ? searchableColumns : allColumns.slice(0, 3)) + .filter((c) => allColumns.includes(c)); + if (cols.length === 0) return ''; + return `WHERE (${cols.map(ilike).join(' OR ')})`; + } + // Single column — use exact match when possible (enables row-group skipping) + return `WHERE ${isExact ? exact(searchColumn) : ilike(searchColumn)}`; +} + +// ─── Component ────────────────────────────────────────────────────────────── + +export default function GtfsViewerClient({ + initialUrl, + embedded = false, +}: GtfsViewerClientProps): React.ReactElement { + // ── URL / load state + const [urlInput, setUrlInput] = useState(initialUrl ?? ''); + const [loadedUrl, setLoadedUrl] = useState(''); + const [metadata, setMetadata] = useState(null); + const [selectedTable, setSelectedTable] = useState(null); + const [parquetUrl, setParquetUrl] = useState(null); + + // ── DuckDB state + const [dbReady, setDbReady] = useState(false); + const [dbError, setDbError] = useState(null); + const connRef = useRef(null); + + // ── Query state + const [columns, setColumns] = useState([]); + const [rows, setRows] = useState[]>([]); + const [totalRows, setTotalRows] = useState(0); + const [page, setPage] = useState(0); + const [rowsPerPage, setRowsPerPage] = useState(50); + const [searchTerm, setSearchTerm] = useState(''); + const [debouncedSearchTerm, setDebouncedSearchTerm] = useState(''); + const [searchColumn, setSearchColumn] = useState('__searchable__'); + const [sortColumn, setSortColumn] = useState(null); + const [sortDir, setSortDir] = useState('asc'); + const [queryLoading, setQueryLoading] = useState(false); + const [queryError, setQueryError] = useState(null); + const [queryMs, setQueryMs] = useState(null); + + // Debounce search term — wait 500 ms after last keystroke before querying + useEffect(() => { + const timer = setTimeout(() => setDebouncedSearchTerm(searchTerm), 500); + return () => clearTimeout(timer); + }, [searchTerm]); + + // ── Initialise DuckDB on mount + useEffect(() => { + getDuckDB() + .then(async (db) => { + const conn = await db.connect(); + connRef.current = conn; + setDbReady(true); + }) + .catch((e) => setDbError(String(e))); + }, []); + + // ── Auto-load when initialUrl is provided and DuckDB is ready + useEffect(() => { + if (dbReady && initialUrl && !loadedUrl) { + setUrlInput(initialUrl); + void handleLoad(initialUrl); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [dbReady, initialUrl]); + + // ── Run query whenever parquet URL or query params change + const runQuery = useCallback(async () => { + const conn = connRef.current; + if (!conn || !parquetUrl) return; + setQueryLoading(true); + setQueryError(null); + const t0 = performance.now(); + try { + const tableMeta = metadata && selectedTable ? metadata.tables[selectedTable] : null; + + // 1. Get column names if we don't have them yet + let cols = columns; + if (cols.length === 0) { + const schemaResult = await conn.query( + `SELECT * FROM read_parquet('${parquetUrl}') LIMIT 0`, + ); + cols = schemaResult.schema.fields.map((f) => f.name); + setColumns(cols); + } + + const searchCols = (tableMeta?.search_columns ?? []).filter((c) => cols.includes(c)); + const where = buildWhere(cols, searchCols, debouncedSearchTerm, searchColumn); + const orderClause = sortColumn ? `ORDER BY "${sortColumn}" ${sortDir.toUpperCase()}` : ''; + const hasFilter = debouncedSearchTerm.trim().length > 0; + + let total: number; + let resultRows: Record[]; + + if (!hasFilter && tableMeta) { + // No filter — row count comes from Parquet metadata (zero extra scan) + total = tableMeta.row_count; + const dataResult = await conn.query( + `SELECT * FROM read_parquet('${parquetUrl}') + ${orderClause} + LIMIT ${rowsPerPage} OFFSET ${page * rowsPerPage}`, + ); + resultRows = dataResult.toArray().map((r) => + Object.fromEntries(cols.map((f) => [f, r[f] ?? null])), + ); + } else { + // Filter active — window function gives count + data in a single scan + const dataResult = await conn.query( + `SELECT *, COUNT(*) OVER() AS __total__ + FROM read_parquet('${parquetUrl}') + ${where} + ${orderClause} + LIMIT ${rowsPerPage} OFFSET ${page * rowsPerPage}`, + ); + const arr = dataResult.toArray(); + total = arr.length > 0 ? Number(arr[0].__total__) : 0; + resultRows = arr.map((r) => Object.fromEntries(cols.map((f) => [f, r[f] ?? null]))); + } + + setTotalRows(total); + setRows(resultRows); + setQueryMs(Math.round(performance.now() - t0)); + } catch (e) { + setQueryError(String(e)); + } finally { + setQueryLoading(false); + } + }, [parquetUrl, columns, debouncedSearchTerm, searchColumn, sortColumn, sortDir, page, rowsPerPage, metadata, selectedTable]); + + useEffect(() => { + if (parquetUrl) runQuery(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [parquetUrl, debouncedSearchTerm, searchColumn, sortColumn, sortDir, page, rowsPerPage]); + + // ── Load URL (metadata.json or direct .parquet) + const handleLoad = async (overrideUrl?: string): Promise => { + const url = (overrideUrl ?? urlInput).trim(); + if (!url) return; + setQueryError(null); + setMetadata(null); + setSelectedTable(null); + setColumns([]); + setRows([]); + setTotalRows(0); + setPage(0); + setSearchTerm(''); + setDebouncedSearchTerm(''); + setSortColumn(null); + setSearchColumn('__searchable__'); + + if (url.endsWith('.parquet') || url.includes('.parquet?')) { + setLoadedUrl(url); + setParquetUrl(url); + } else { + // Assume metadata.json + try { + const res = await fetch(url); + if (!res.ok) throw new Error(`HTTP ${res.status}`); + const meta: GtfsMetadata = await res.json(); + setMetadata(meta); + setLoadedUrl(url); + // Auto-select first table + const firstTable = Object.keys(meta.tables)[0]; + if (firstTable) { + setSelectedTable(firstTable); + setParquetUrl(resolveParquetUrl(url, meta.tables[firstTable].file)); + } + } catch (e) { + setQueryError(`Failed to load: ${String(e)}`); + } + } + }; + + const handleTableSelect = (tableName: string): void => { + if (!metadata) return; + setSelectedTable(tableName); + setColumns([]); + setRows([]); + setTotalRows(0); + setPage(0); + setSearchTerm(''); + setDebouncedSearchTerm(''); + setSortColumn(null); + setSearchColumn('__searchable__'); + setParquetUrl(resolveParquetUrl(loadedUrl, metadata.tables[tableName].file)); + }; + + const handleSort = (col: string): void => { + if (sortColumn === col) { + setSortDir((d) => (d === 'asc' ? 'desc' : 'asc')); + } else { + setSortColumn(col); + setSortDir('asc'); + } + setPage(0); + }; + + const currentTableMeta = metadata && selectedTable ? metadata.tables[selectedTable] : null; + + // ─── Render ──────────────────────────────────────────────────────────────── + return ( + + {/* Header — hidden in embedded mode */} + {!embedded && ( + <> + + + GTFS Viewer + + + + + + + + Paste a metadata.json URL (from gtfs-to-parquet.sh output) or a + direct .parquet URL. Files are queried via HTTP Range requests — only the + rows you see are downloaded. + + + )} + + {/* DuckDB status */} + {!dbReady && !dbError && ( + } sx={{ mb: 2 }}> + Loading DuckDB-WASM engine (~6 MB, cached after first load)… + + )} + {dbError && ( + + DuckDB failed to initialise: {dbError} + + )} + + {/* URL input — hidden in embedded mode (URL is pre-set) */} + {!embedded && ( + + + setUrlInput(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && handleLoad()} + disabled={!dbReady} + /> + + + {metadata && ( + + Source: {metadata.source} · Generated: {new Date(metadata.generated_at).toLocaleString()} + + )} + + )} + + + {/* Table sidebar */} + {metadata && ( + + + Tables + + + {Object.entries(metadata.tables).map(([name, meta]) => ( + handleTableSelect(name)} + sx={{ + px: 2, + py: 1, + cursor: 'pointer', + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + bgcolor: selectedTable === name ? 'action.selected' : 'transparent', + '&:hover': { bgcolor: 'action.hover' }, + borderLeft: selectedTable === name ? '3px solid' : '3px solid transparent', + borderColor: selectedTable === name ? 'primary.main' : 'transparent', + }} + > + + {name} + + 500_000 ? 'warning' : 'default'} + sx={{ fontSize: 10, height: 18 }} + /> + + ))} + + )} + + {/* Main content */} + + {/* Toolbar */} + {parquetUrl && ( + + + {/* Global / column search */} + + Search in + + + + { setSearchTerm(e.target.value); setPage(0); }} + sx={{ flex: 1 }} + InputProps={{ + startAdornment: ( + + + + ), + endAdornment: searchTerm && ( + + { setSearchTerm(''); setPage(0); }}> + + + + ), + }} + /> + + {/* Stats */} + + {queryMs !== null && ( + + )} + {currentTableMeta && ( + + )} + + + + + + + + + )} + + {/* Error */} + {queryError && ( + setQueryError(null)}> + {queryError} + + )} + + {/* Table */} + {parquetUrl && ( + + + + + + {(queryLoading && columns.length === 0 + ? (currentTableMeta?.columns ?? Array.from({ length: 6 }, (_, i) => `col_${i}`)) + : columns + ).map((col) => ( + + handleSort(col)} + > + + {col} + {(currentTableMeta?.search_columns ?? []).includes(col) && ( + + + + )} + + + + ))} + + + + {queryLoading + ? Array.from({ length: Math.min(rowsPerPage, 10) }).map((_, i) => ( + + {(columns.length > 0 ? columns : Array.from({ length: 6 })).map((_, j) => ( + + + + ))} + + )) + : rows.map((row, i) => ( + + {columns.map((col) => ( + + {String(row[col] ?? '')} + + ))} + + ))} + +
+
+ + setPage(p)} + rowsPerPage={rowsPerPage} + onRowsPerPageChange={(e) => { setRowsPerPage(Number(e.target.value)); setPage(0); }} + rowsPerPageOptions={[25, 50, 100, 250]} + labelDisplayedRows={({ from, to, count }) => + `${from}–${to} of ${fmtRows(count)} rows` + } + /> +
+ )} + + {/* Empty state */} + {!parquetUrl && dbReady && ( + + {embedded ? ( + + Parquet data not yet available for this dataset. Run{' '} + gtfs-to-parquet.sh to generate it. + + ) : ( + <> + + No file loaded + + + Generate Parquet files locally: + + + {`# In mobility-feed-api repo:\n./scripts/gtfs-to-parquet.sh --url "https://storage.googleapis.com/mdb-latest/mdb-10.zip"\n\n# Then serve locally:\ncd ./gtfs_parquet_output && python3 -m http.server 8888\n\n# Paste in the field above:\nhttp://localhost:8888/metadata.json`} + + + )} + + )} +
+
+
+ ); +} diff --git a/src/app/screens/Feed/FeedView.tsx b/src/app/screens/Feed/FeedView.tsx index 202d66d..5564860 100644 --- a/src/app/screens/Feed/FeedView.tsx +++ b/src/app/screens/Feed/FeedView.tsx @@ -64,6 +64,11 @@ const PreviousDatasets = dynamic( {}, ); +const GtfsDataViewer = dynamic( + async () => + await import('./components/GtfsDataViewer').then((mod) => mod.default), +); + interface Props { feed: BasicFeedType; initialDatasets?: Array; @@ -421,6 +426,12 @@ export default async function FeedView({ /> )} + + {feed.data_type === 'gtfs' && latestDataset?.hosted_url != null && ( + + + + )} diff --git a/src/app/screens/Feed/components/GtfsDataViewer.tsx b/src/app/screens/Feed/components/GtfsDataViewer.tsx new file mode 100644 index 0000000..22c7515 --- /dev/null +++ b/src/app/screens/Feed/components/GtfsDataViewer.tsx @@ -0,0 +1,144 @@ +'use client'; + +/** + * GtfsDataViewer + * + * Embeds the GTFS table viewer inside the feed detail page as a lazy-loaded + * accordion. DuckDB-WASM is only downloaded when the user intentionally + * expands the section — never on initial page load. + * + * The Parquet metadata URL is derived from the dataset's hosted_url using + * the convention established by gtfs-to-parquet.sh: + * https://files.mobilitydatabase.org/{feed}/{dataset}/{dataset}.zip + * → https://files.mobilitydatabase.org/{feed}/{dataset}/gtfs_parquet/metadata.json + */ + +import React, { useState } from 'react'; +import dynamic from 'next/dynamic'; +import { + Accordion, + AccordionDetails, + AccordionSummary, + Alert, + Box, + Chip, + CircularProgress, + Skeleton, + Stack, + Typography, +} from '@mui/material'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import TableChartOutlinedIcon from '@mui/icons-material/TableChartOutlined'; +import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; + +// next/dynamic with ssr:false prevents Turbopack from statically analysing +// the DuckDB-WASM package at build time (avoids the Turbopack WASM crash). +// The component is only loaded client-side when the accordion is expanded. +const GtfsViewerClient = dynamic( + () => import('../../../components/gtfs-viewer/GtfsViewerClient'), + { + ssr: false, + loading: () => ( + + + + + Loading DuckDB-WASM engine… + + + {Array.from({ length: 5 }).map((_, i) => ( + + ))} + + ), + }, +); + +// ─── Helpers ──────────────────────────────────────────────────────────────── + +/** + * Derives the Parquet metadata.json URL from a dataset's hosted ZIP URL. + * Convention: replace the .zip filename with gtfs_parquet/metadata.json + * + * Input: https://files.mobilitydatabase.org/mdb-2014/mdb-2014-20250708/mdb-2014-20250708.zip + * Output: https://files.mobilitydatabase.org/mdb-2014/mdb-2014-20250708/gtfs_parquet/metadata.json + */ +function deriveParquetMetaUrl(hostedUrl: string): string { + const lastSlash = hostedUrl.lastIndexOf('/'); + const base = hostedUrl.substring(0, lastSlash); + return `${base}/gtfs_parquet/metadata.json`; +} + +// ─── Props ─────────────────────────────────────────────────────────────────── + +interface GtfsDataViewerProps { + /** The dataset's hosted ZIP URL (from latestDataset.hosted_url) */ + hostedUrl: string; +} + +// ─── Component ─────────────────────────────────────────────────────────────── + +export default function GtfsDataViewer({ hostedUrl }: GtfsDataViewerProps): React.ReactElement { + const [expanded, setExpanded] = useState(false); + const parquetMetaUrl = deriveParquetMetaUrl(hostedUrl); + + return ( + setExpanded(isExpanded)} + disableGutters + elevation={0} + sx={{ + border: '1px solid', + borderColor: 'divider', + borderRadius: '6px !important', + '&:before': { display: 'none' }, // remove MUI default top divider line + bgcolor: 'background.paper', + }} + > + } + sx={{ + px: 3, + py: 1, + '& .MuiAccordionSummary-content': { alignItems: 'center', gap: 1 }, + }} + > + + Explore Dataset Tables + + {!expanded && ( + + Browse stops, routes, trips, stop_times and more + + )} + + + + {expanded && ( + <> + } + sx={{ + mx: 2, + mt: 2, + mb: 0, + fontSize: 12, + py: 0.5, + '& .MuiAlert-message': { py: 0.5 }, + }} + > + POC: Queries run entirely in your browser via DuckDB-WASM + HTTP Range + requests. Only the rows you see are downloaded — no full file transfer. + + + + + + + )} + + + ); +} diff --git a/yarn.lock b/yarn.lock index 8fee1ed..36a44db 100644 --- a/yarn.lock +++ b/yarn.lock @@ -488,6 +488,14 @@ enabled "2.0.x" kuler "^2.0.0" +"@duckdb/duckdb-wasm@^1.33.1-dev45.0": + version "1.33.1-dev45.0" + resolved "https://registry.yarnpkg.com/@duckdb/duckdb-wasm/-/duckdb-wasm-1.33.1-dev45.0.tgz#2bc0283d14da0b160a3da1fd0db3195e2ca08874" + integrity sha512-ETlrjhiGQzNdaOhpro/Y9u/RCcK+iyuczLy7uOn0kG5Mqlj8C+gTuhBXjs4JpK9ocdUgr3oT8zYYIbUnFD9AYA== + dependencies: + apache-arrow "^17.0.0" + qs "^6.14.1" + "@electric-sql/pglite-tools@^0.2.8": version "0.2.20" resolved "https://registry.yarnpkg.com/@electric-sql/pglite-tools/-/pglite-tools-0.2.20.tgz#6d1c3d4e2d45a0dd8f91c962f5390feed75a1472" @@ -3528,6 +3536,13 @@ dependencies: tslib "^2.8.0" +"@swc/helpers@^0.5.11": + version "0.5.23" + resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.23.tgz#19287d0d86d962b111376039a50c792902c9a86a" + integrity sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw== + dependencies: + tslib "^2.8.0" + "@swc/jest@^0.2.39": version "0.2.39" resolved "https://registry.yarnpkg.com/@swc/jest/-/jest-0.2.39.tgz#482bee0adb0726fab1487a4f902a278ec563a6b7" @@ -3723,6 +3738,16 @@ resolved "https://registry.yarnpkg.com/@types/caseless/-/caseless-0.12.5.tgz#db9468cb1b1b5a925b8f34822f1669df0c5472f5" integrity sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg== +"@types/command-line-args@^5.2.3": + version "5.2.3" + resolved "https://registry.yarnpkg.com/@types/command-line-args/-/command-line-args-5.2.3.tgz#553ce2fd5acf160b448d307649b38ffc60d39639" + integrity sha512-uv0aG6R0Y8WHZLTamZwtfsDLVRnOa+n+n5rEvFWL5Na5gZ8V2Teab/duDPFzIIIhs9qizDpcavCusCLJZu62Kw== + +"@types/command-line-usage@^5.0.4": + version "5.0.4" + resolved "https://registry.yarnpkg.com/@types/command-line-usage/-/command-line-usage-5.0.4.tgz#374e4c62d78fbc5a670a0f36da10235af879a0d5" + integrity sha512-BwR5KP3Es/CSht0xqBcUXS3qCAUVXwpRKsV2+arxeb65atasuXG9LykC9Ab10Cw3s2raH92ZqOeILaQbsB2ACg== + "@types/connect@3.4.38": version "3.4.38" resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" @@ -3917,6 +3942,13 @@ dependencies: undici-types "~7.16.0" +"@types/node@^20.13.0": + version "20.19.42" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.19.42.tgz#002109ec605f2a73f46e9677139d8b27a2d88394" + integrity sha512-5L7SUaFC1RyDraj2yRhyBzHTobyXHmohD100CChNtyPyleoq37Mqab5Gn8XEKI04dfN/oqPdpHk38MgcQWHbZg== + dependencies: + undici-types "~6.21.0" + "@types/node@^22.8.7": version "22.19.7" resolved "https://registry.yarnpkg.com/@types/node/-/node-22.19.7.tgz#434094ee1731ae76c16083008590a5835a8c39c1" @@ -4611,6 +4643,21 @@ anymatch@^3.1.3, anymatch@~3.1.2: normalize-path "^3.0.0" picomatch "^2.0.4" +apache-arrow@^17.0.0: + version "17.0.0" + resolved "https://registry.yarnpkg.com/apache-arrow/-/apache-arrow-17.0.0.tgz#73d98566c86352c9a0314c03890dbd7211073827" + integrity sha512-X0p7auzdnGuhYMVKYINdQssS4EcKec9TCXyez/qtJt32DrIMGbzqiaMiQ0X6fQlQpw8Fl0Qygcv4dfRAr5Gu9Q== + dependencies: + "@swc/helpers" "^0.5.11" + "@types/command-line-args" "^5.2.3" + "@types/command-line-usage" "^5.0.4" + "@types/node" "^20.13.0" + command-line-args "^5.2.1" + command-line-usage "^7.0.1" + flatbuffers "^24.3.25" + json-bignum "^0.0.3" + tslib "^2.6.2" + arch@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/arch/-/arch-2.2.0.tgz#1bc47818f305764f23ab3306b0bfc086c5a29d11" @@ -4676,6 +4723,16 @@ arr-union@^3.1.0: resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" integrity sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q== +array-back@^3.0.1, array-back@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/array-back/-/array-back-3.1.0.tgz#b8859d7a508871c9a7b2cf42f99428f65e96bfb0" + integrity sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q== + +array-back@^6.2.2: + version "6.2.3" + resolved "https://registry.yarnpkg.com/array-back/-/array-back-6.2.3.tgz#d41e67598805e614f23b319b9e5960dfbcd72ae2" + integrity sha512-SGDvmg6QTYiTxCBkYVmThcoa67uLl35pyzRHdpCGBOcqFy6BtwnphoFPk7LhJshD+Yk1Kt35WGWeZPTgwR4Fhw== + array-buffer-byte-length@^1.0.1, array-buffer-byte-length@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz#384d12a37295aec3769ab022ad323a18a51ccf8b" @@ -5283,6 +5340,13 @@ caseless@~0.12.0: resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw== +chalk-template@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/chalk-template/-/chalk-template-0.4.0.tgz#692c034d0ed62436b9062c1707fadcd0f753204b" + integrity sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg== + dependencies: + chalk "^4.1.2" + chalk@4.1.2, chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2, chalk@~4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" @@ -5527,6 +5591,26 @@ combined-stream@^1.0.8, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" +command-line-args@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/command-line-args/-/command-line-args-5.2.1.tgz#c44c32e437a57d7c51157696893c5909e9cec42e" + integrity sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg== + dependencies: + array-back "^3.1.0" + find-replace "^3.0.0" + lodash.camelcase "^4.3.0" + typical "^4.0.0" + +command-line-usage@^7.0.1: + version "7.0.4" + resolved "https://registry.yarnpkg.com/command-line-usage/-/command-line-usage-7.0.4.tgz#759449bac39c5410e23513f1f78551b669df1514" + integrity sha512-85UdvzTNx/+s5CkSgBm/0hzP80RFHAa7PsfeADE5ezZF3uHz3/Tqj9gIKGT9PTtpycc3Ua64T0oVulGfKxzfqg== + dependencies: + array-back "^6.2.2" + chalk-template "^0.4.0" + table-layout "^4.1.1" + typical "^7.3.0" + commander@^10.0.0: version "10.0.1" resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" @@ -7344,6 +7428,13 @@ find-process@2.0.0: commander "^12.1.0" loglevel "^1.9.2" +find-replace@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-replace/-/find-replace-3.0.0.tgz#3e7e23d3b05167a76f770c9fbd5258b0def68c38" + integrity sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ== + dependencies: + array-back "^3.0.1" + find-root@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" @@ -7509,6 +7600,11 @@ flat-cache@^3.0.4: keyv "^4.5.3" rimraf "^3.0.2" +flatbuffers@^24.3.25: + version "24.12.23" + resolved "https://registry.yarnpkg.com/flatbuffers/-/flatbuffers-24.12.23.tgz#6eea59d2bcda0c5d59bcacefd6216348b3086883" + integrity sha512-dLVCAISd5mhls514keQzmEG6QHmUUsNuWsb4tFafIUwvvgDjXhtfAYSKOzt5SWOy+qByV5pbsDZ+Vb7HUOBEdA== + flatted@^3.2.9: version "3.3.3" resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358" @@ -9367,6 +9463,11 @@ json-bigint@^1.0.0: dependencies: bignumber.js "^9.0.0" +json-bignum@^0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/json-bignum/-/json-bignum-0.0.3.tgz#41163b50436c773d82424dbc20ed70db7604b8d7" + integrity sha512-2WHyXj3OfHSgNyuzDbSxI1w2jgw5gkWSWhS7Qg4bWXx1nLk3jnbwfUeS0PSba3IzpTUWdHxBieELUzXRjQB2zg== + json-buffer@3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" @@ -12621,6 +12722,14 @@ systeminformation@^5.27.14: resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-5.30.6.tgz#c100cb632bbb955fc44ba033f624da22c3a6a5be" integrity sha512-LEIyK1aEv5P3BhAPW3swdlIyCihxwEq/Gki+kcONieU4PIeRCSLDuGkk0Va/56PSBgjVgEksOM88dmY6YqOyfQ== +table-layout@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/table-layout/-/table-layout-4.1.1.tgz#0f72965de1a5c0c1419c9ba21cae4e73a2f73a42" + integrity sha512-iK5/YhZxq5GO5z8wb0bY1317uDF3Zjpha0QFFLA8/trAoiLbQD0HUbMesEaxyzUgDxi2QlcbM8IvqOlEjgoXBA== + dependencies: + array-back "^6.2.2" + wordwrapjs "^5.1.0" + tagged-tag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/tagged-tag/-/tagged-tag-1.0.0.tgz#a0b5917c2864cba54841495abfa3f6b13edcf4d6" @@ -12915,7 +13024,7 @@ tsconfig-paths@^3.15.0: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@2, tslib@^2.0.0, tslib@^2.0.1, tslib@^2.1.0, tslib@^2.4.0, tslib@^2.8.0: +tslib@2, tslib@^2.0.0, tslib@^2.0.1, tslib@^2.1.0, tslib@^2.4.0, tslib@^2.6.2, tslib@^2.8.0: version "2.8.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== @@ -13101,6 +13210,16 @@ typewise@^1.0.3: dependencies: typewise-core "^1.2.0" +typical@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/typical/-/typical-4.0.0.tgz#cbeaff3b9d7ae1e2bbfaf5a4e6f11eccfde94fc4" + integrity sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw== + +typical@^7.3.0: + version "7.3.0" + resolved "https://registry.yarnpkg.com/typical/-/typical-7.3.0.tgz#930376be344228709f134613911fa22aa09617a4" + integrity sha512-ya4mg/30vm+DOWfBg4YK3j2WD6TWtRkCbasOJr40CseYENzCUby/7rIvXA99JGsQHeNxLbnXdyLLxKSv3tauFw== + unbox-primitive@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.1.0.tgz#8d9d2c9edeea8460c7f35033a88867944934d1e2" @@ -13570,6 +13689,11 @@ word-wrap@^1.2.5: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== +wordwrapjs@^5.1.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/wordwrapjs/-/wordwrapjs-5.1.1.tgz#bfd1eb426f0f7eec73b7df32cf7df1f618bfb3a9" + integrity sha512-0yweIbkINJodk27gX9LBGMzyQdBDan3s/dEAiwBOj+Mf0PPyWL6/rikalkv8EeD0E8jm4o5RXEOrFTP3NXbhJg== + "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"