From 1283f6487f1213432e979cbd2ec284302dcdf8a1 Mon Sep 17 00:00:00 2001 From: bkellam Date: Tue, 10 Sep 2024 21:55:00 -0700 Subject: [PATCH] Add page to list indexed repositories --- package.json | 2 + public/sb_logo_dark_small.png | Bin 0 -> 25919 bytes public/sb_logo_light_small.png | Bin 0 -> 23451 bytes src/app/api/(server)/repos/route.ts | 8 ++ src/app/navigationMenu.tsx | 79 +++++++++++++++ src/app/page.tsx | 23 +---- src/app/repos/columns.tsx | 129 ++++++++++++++++++++++++ src/app/repos/page.tsx | 43 ++++++++ src/components/ui/data-table.tsx | 136 ++++++++++++++++++++++++++ src/components/ui/navigation-menu.tsx | 128 ++++++++++++++++++++++++ src/components/ui/table.tsx | 117 ++++++++++++++++++++++ src/lib/schemas.ts | 67 ++++++++++++- src/lib/server/searchService.ts | 32 +++++- yarn.lock | 44 +++++++++ 14 files changed, 781 insertions(+), 27 deletions(-) create mode 100644 public/sb_logo_dark_small.png create mode 100644 public/sb_logo_light_small.png create mode 100644 src/app/api/(server)/repos/route.ts create mode 100644 src/app/navigationMenu.tsx create mode 100644 src/app/repos/columns.tsx create mode 100644 src/app/repos/page.tsx create mode 100644 src/components/ui/data-table.tsx create mode 100644 src/components/ui/navigation-menu.tsx create mode 100644 src/components/ui/table.tsx diff --git a/package.json b/package.json index 7e4b0476..e9bdc94d 100644 --- a/package.json +++ b/package.json @@ -18,11 +18,13 @@ "@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.1.0", + "@radix-ui/react-navigation-menu": "^1.2.0", "@radix-ui/react-scroll-area": "^1.1.0", "@radix-ui/react-separator": "^1.1.0", "@radix-ui/react-slot": "^1.1.0", "@replit/codemirror-vim": "^6.2.1", "@tanstack/react-query": "^5.53.3", + "@tanstack/react-table": "^8.20.5", "@uiw/react-codemirror": "^4.23.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", diff --git a/public/sb_logo_dark_small.png b/public/sb_logo_dark_small.png new file mode 100644 index 0000000000000000000000000000000000000000..8aae8a557cc7a4386ad0817f922b2431a3d45da1 GIT binary patch literal 25919 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 { + const response = await listRepositories(); + return Response.json(response); +} \ No newline at end of file diff --git a/src/app/navigationMenu.tsx b/src/app/navigationMenu.tsx new file mode 100644 index 00000000..2f20319e --- /dev/null +++ b/src/app/navigationMenu.tsx @@ -0,0 +1,79 @@ +'use client'; + +import { Button } from "@/components/ui/button"; +import { NavigationMenu as NavigationMenuBase, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, navigationMenuTriggerStyle } from "@/components/ui/navigation-menu"; +import Link from "next/link"; +import { GitHubLogoIcon } from "@radix-ui/react-icons"; +import { SettingsDropdown } from "./settingsDropdown"; +import { Separator } from "@/components/ui/separator"; +import Image from "next/image"; +import logoDark from "../../public/sb_logo_dark_small.png"; +import logoLight from "../../public/sb_logo_light_small.png"; +import { useRouter } from "next/navigation"; + +const SOURCEBOT_GITHUB_URL = "https://github.com/TaqlaAI/sourcebot"; + +export const NavigationMenu = () => { + const router = useRouter(); + + return ( +

+
+
+
{ + router.push("/"); + }} + > + {"Sourcebot + {"Sourcebot +
+ + + + + + + Search + + + + + + + Repositories + + + + + +
+ +
+ + +
+
+ +
+ + + ) +} \ No newline at end of file diff --git a/src/app/page.tsx b/src/app/page.tsx index 6dd04265..32c7da01 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -3,32 +3,15 @@ import Image from "next/image"; import logoDark from "../../public/sb_logo_dark_large.png"; import logoLight from "../../public/sb_logo_light_large.png"; +import { NavigationMenu } from "./navigationMenu"; import { SearchBar } from "./searchBar"; -import { SettingsDropdown } from "./settingsDropdown"; -import { GitHubLogoIcon } from "@radix-ui/react-icons"; -import { Button } from "@/components/ui/button"; - -const SOURCEBOT_GITHUB_URL = "https://github.com/TaqlaAI/sourcebot"; export default function Home() { - return (
{/* TopBar */} -
-
- - -
-
+ +
[] = [ + { + accessorKey: "name", + header: "Name", + }, + { + accessorKey: "branches", + header: "Branches", + cell: ({ row }) => { + const branches = row.original.branches; + return ( +
+ {branches.map(({ name, version }, index) => { + const shortVersion = version.substring(0, 8); + return ( + + {name} + @ + { + const url = row.original.commitUrlTemplate.replace("{{.Version}}", version); + window.open(url, "_blank"); + }} + > + {shortVersion} + + + ) + })} +
+ ); + }, + }, + { + accessorKey: "shardCount", + header: ({ column }) => createSortHeader("Shard Count", column), + cell: ({ row }) => ( +
{row.original.shardCount}
+ ) + }, + { + accessorKey: "indexedFiles", + header: ({ column }) => createSortHeader("Indexed Files", column), + cell: ({ row }) => ( +
{row.original.indexedFiles}
+ ) + }, + { + accessorKey: "indexSizeBytes", + header: ({ column }) => createSortHeader("Index Size", column), + cell: ({ row }) => { + const size = getFormattedSizeString(row.original.indexSizeBytes); + return
{size}
; + } + }, + { + accessorKey: "repoSizeBytes", + header: ({ column }) => createSortHeader("Repository Size", column), + cell: ({ row }) => { + const size = getFormattedSizeString(row.original.repoSizeBytes); + return
{size}
; + } + }, + { + accessorKey: "lastIndexed", + header: ({ column }) => createSortHeader("Last Indexed", column), + cell: ({ row }) => { + const date = new Date(row.original.lastIndexed); + return date.toISOString(); + } + }, + { + accessorKey: "latestCommit", + header: ({ column }) => createSortHeader("Latest Commit", column), + cell: ({ row }) => { + const date = new Date(row.original.latestCommit); + return date.toISOString(); + } + } +] + +const getFormattedSizeString = (bytes: number): string => { + let size = bytes; + const units = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB']; + let unitIndex = 0; + + while (size >= 1024 && unitIndex < units.length - 1) { + size /= 1024; + unitIndex++; + } + + return `${size.toFixed(2)} ${units[unitIndex]}`; +} + +const createSortHeader = (name: string, column: Column) => { + return ( + + ) +} \ No newline at end of file diff --git a/src/app/repos/page.tsx b/src/app/repos/page.tsx new file mode 100644 index 00000000..b5b91be9 --- /dev/null +++ b/src/app/repos/page.tsx @@ -0,0 +1,43 @@ +import { NavigationMenu } from "../navigationMenu"; +import { DataTable } from "@/components/ui/data-table"; +import { columns, RepositoryColumnInfo } from "./columns"; +import { listRepositories } from "@/lib/server/searchService"; +import { isServiceError } from "@/lib/utils"; + +export default async function ReposPage() { + const _repos = await listRepositories(); + + if (isServiceError(_repos)) { + return
Error fetching repositories
; + } + const repos = _repos.List.Repos.map((repo): RepositoryColumnInfo => { + return { + name: repo.Repository.Name, + branches: repo.Repository.Branches.map((branch) => { + return { + name: branch.Name, + version: branch.Version, + } + }), + repoSizeBytes: repo.Stats.ContentBytes, + indexSizeBytes: repo.Stats.IndexBytes, + shardCount: repo.Stats.Shards, + lastIndexed: repo.IndexMetadata.IndexTime, + latestCommit: repo.Repository.LatestCommitDate, + indexedFiles: repo.Stats.Documents, + commitUrlTemplate: repo.Repository.CommitURLTemplate, + } + }); + + return ( +
+ + +
+ ) +} \ No newline at end of file diff --git a/src/components/ui/data-table.tsx b/src/components/ui/data-table.tsx new file mode 100644 index 00000000..a328d833 --- /dev/null +++ b/src/components/ui/data-table.tsx @@ -0,0 +1,136 @@ +"use client" + +import { + ColumnDef, + ColumnFiltersState, + SortingState, + flexRender, + getCoreRowModel, + getFilteredRowModel, + getPaginationRowModel, + getSortedRowModel, + useReactTable, +} from "@tanstack/react-table" +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import * as React from "react" + + +interface DataTableProps { + columns: ColumnDef[] + data: TData[] + searchKey: string + searchPlaceholder?: string +} + +export function DataTable({ + columns, + data, + searchKey, + searchPlaceholder, +}: DataTableProps) { + const [sorting, setSorting] = React.useState([]) + const [columnFilters, setColumnFilters] = React.useState( + [] + ) + + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + getPaginationRowModel: getPaginationRowModel(), + onSortingChange: setSorting, + getSortedRowModel: getSortedRowModel(), + onColumnFiltersChange: setColumnFilters, + getFilteredRowModel: getFilteredRowModel(), + state: { + sorting, + columnFilters, + }, + }) + + return ( +
+
+ + table.getColumn(searchKey)?.setFilterValue(event.target.value) + } + className="max-w-sm" + /> +
+
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() + )} + + ) + })} + + ))} + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + )) + ) : ( + + + No results. + + + )} + +
+
+
+ + +
+
+ ) +} diff --git a/src/components/ui/navigation-menu.tsx b/src/components/ui/navigation-menu.tsx new file mode 100644 index 00000000..1419f566 --- /dev/null +++ b/src/components/ui/navigation-menu.tsx @@ -0,0 +1,128 @@ +import * as React from "react" +import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu" +import { cva } from "class-variance-authority" +import { ChevronDown } from "lucide-react" + +import { cn } from "@/lib/utils" + +const NavigationMenu = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + {children} + + +)) +NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName + +const NavigationMenuList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName + +const NavigationMenuItem = NavigationMenuPrimitive.Item + +const navigationMenuTriggerStyle = cva( + "group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50" +) + +const NavigationMenuTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + {children}{" "} + +)) +NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName + +const NavigationMenuContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName + +const NavigationMenuLink = NavigationMenuPrimitive.Link + +const NavigationMenuViewport = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( +
+ +
+)) +NavigationMenuViewport.displayName = + NavigationMenuPrimitive.Viewport.displayName + +const NavigationMenuIndicator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +
+ +)) +NavigationMenuIndicator.displayName = + NavigationMenuPrimitive.Indicator.displayName + +export { + navigationMenuTriggerStyle, + NavigationMenu, + NavigationMenuList, + NavigationMenuItem, + NavigationMenuContent, + NavigationMenuTrigger, + NavigationMenuLink, + NavigationMenuIndicator, + NavigationMenuViewport, +} diff --git a/src/components/ui/table.tsx b/src/components/ui/table.tsx new file mode 100644 index 00000000..7f3502f8 --- /dev/null +++ b/src/components/ui/table.tsx @@ -0,0 +1,117 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +const Table = React.forwardRef< + HTMLTableElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+ + +)) +Table.displayName = "Table" + +const TableHeader = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableHeader.displayName = "TableHeader" + +const TableBody = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableBody.displayName = "TableBody" + +const TableFooter = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + tr]:last:border-b-0", + className + )} + {...props} + /> +)) +TableFooter.displayName = "TableFooter" + +const TableRow = React.forwardRef< + HTMLTableRowElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableRow.displayName = "TableRow" + +const TableHead = React.forwardRef< + HTMLTableCellElement, + React.ThHTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +TableHead.displayName = "TableHead" + +const TableCell = React.forwardRef< + HTMLTableCellElement, + React.TdHTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableCell.displayName = "TableCell" + +const TableCaption = React.forwardRef< + HTMLTableCaptionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +TableCaption.displayName = "TableCaption" + +export { + Table, + TableHeader, + TableBody, + TableFooter, + TableHead, + TableRow, + TableCell, + TableCaption, +} diff --git a/src/lib/schemas.ts b/src/lib/schemas.ts index c96fed73..5da6ec65 100644 --- a/src/lib/schemas.ts +++ b/src/lib/schemas.ts @@ -17,11 +17,11 @@ export type SearchResultLocation = z.infer; // @see : https://github.com/TaqlaAI/zoekt/blob/main/api.go#L212 const locationSchema = z.object({ - // 0-based byte offset from the beginning of the file + // 0-based byte offset from the beginning of the file ByteOffset: z.number(), - // 1-based line number from the beginning of the file + // 1-based line number from the beginning of the file LineNumber: z.number(), - // 1-based column number (in runes) from the beginning of line + // 1-based column number (in runes) from the beginning of line Column: z.number(), }); @@ -66,4 +66,63 @@ export type FileSourceResponse = z.infer; export const fileSourceResponseSchema = z.object({ source: z.string(), -}); \ No newline at end of file +}); + + +export type ListRepositoriesResponse = z.infer; + +// @see : https://github.com/TaqlaAI/zoekt/blob/3780e68cdb537d5a7ed2c84d9b3784f80c7c5d04/api.go#L728 +export const statsSchema = z.object({ + Repos: z.number(), + Shards: z.number(), + Documents: z.number(), + IndexBytes: z.number(), + ContentBytes: z.number(), + NewLinesCount: z.number(), + DefaultBranchNewLinesCount: z.number(), + OtherBranchesNewLinesCount: z.number(), +}); + +// @see : https://github.com/TaqlaAI/zoekt/blob/3780e68cdb537d5a7ed2c84d9b3784f80c7c5d04/api.go#L716 +export const indexMetadataSchema = z.object({ + IndexFormatVersion: z.number(), + IndexFeatureVersion: z.number(), + IndexMinReaderVersion: z.number(), + IndexTime: z.string(), + PlainASCII: z.boolean(), + LanguageMap: z.record(z.string(), z.number()), + ZoektVersion: z.string(), + ID: z.string(), +}); + +// @see : https://github.com/TaqlaAI/zoekt/blob/3780e68cdb537d5a7ed2c84d9b3784f80c7c5d04/api.go#L555 +export const repositorySchema = z.object({ + Name: z.string(), + URL: z.string(), + Source: z.string(), + Branches: z.array(z.object({ + Name: z.string(), + Version: z.string(), + })), + CommitURLTemplate: z.string(), + FileURLTemplate: z.string(), + LineFragmentTemplate: z.string(), + RawConfig: z.record(z.string(), z.string()), + Rank: z.number(), + IndexOptions: z.string(), + HasSymbols: z.boolean(), + Tombstone: z.boolean(), + LatestCommitDate: z.string(), + FileTombstones: z.string().optional(), +}); + +export const listRepositoriesResponseSchema = z.object({ + List: z.object({ + Repos: z.array(z.object({ + Repository: repositorySchema, + IndexMetadata: indexMetadataSchema, + Stats: statsSchema, + })), + Stats: statsSchema, + }) +}); diff --git a/src/lib/server/searchService.ts b/src/lib/server/searchService.ts index 924363aa..330408ba 100644 --- a/src/lib/server/searchService.ts +++ b/src/lib/server/searchService.ts @@ -1,9 +1,9 @@ +import escapeStringRegexp from "escape-string-regexp"; import { SHARD_MAX_MATCH_COUNT, TOTAL_MAX_MATCH_COUNT } from "../environment"; -import { FileSourceRequest, FileSourceResponse, SearchRequest, SearchResponse, searchResponseSchema } from "../schemas"; -import { fileNotFound, invalidZoektResponse, schemaValidationError, ServiceError, unexpectedError } from "../serviceError"; +import { FileSourceRequest, FileSourceResponse, ListRepositoriesResponse, listRepositoriesResponseSchema, SearchRequest, SearchResponse, searchResponseSchema } from "../schemas"; +import { fileNotFound, invalidZoektResponse, ServiceError, unexpectedError } from "../serviceError"; import { isServiceError } from "../utils"; import { zoektFetch } from "./zoektClient"; -import escapeStringRegexp from "escape-string-regexp"; export const search = async ({ query, numResults, whole }: SearchRequest): Promise => { const body = JSON.stringify({ @@ -63,4 +63,30 @@ export const getFileSource = async ({ fileName, repository }: FileSourceRequest) return { source } +} + +export const listRepositories = async (): Promise => { + const body = JSON.stringify({ + opts: { + Field: 0, + } + }); + const listResponse = await zoektFetch({ + path: "/api/list", + body, + method: "POST" + }); + + if (!listResponse.ok) { + return invalidZoektResponse(listResponse); + } + + const listBody = await listResponse.json(); + const parsedListResponse = listRepositoriesResponseSchema.safeParse(listBody); + if (!parsedListResponse.success) { + console.error(`Failed to parse zoekt response. Error: ${parsedListResponse.error}`); + return unexpectedError(`Something went wrong while parsing the response from zoekt`); + } + + return parsedListResponse.data; } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index ad3e17ff..bfca08f7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -582,6 +582,26 @@ aria-hidden "^1.1.1" react-remove-scroll "2.5.7" +"@radix-ui/react-navigation-menu@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.0.tgz#884c9b9fd141cc5db257bd3f6bf3b84e349c6617" + integrity sha512-OQ8tcwAOR0DhPlSY3e4VMXeHiol7la4PPdJWhhwJiJA+NLX0SaCaonOkRnI3gCDHoZ7Fo7bb/G6q25fRM2Y+3Q== + dependencies: + "@radix-ui/primitive" "1.1.0" + "@radix-ui/react-collection" "1.1.0" + "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/react-context" "1.1.0" + "@radix-ui/react-direction" "1.1.0" + "@radix-ui/react-dismissable-layer" "1.1.0" + "@radix-ui/react-id" "1.1.0" + "@radix-ui/react-presence" "1.1.0" + "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-use-callback-ref" "1.1.0" + "@radix-ui/react-use-controllable-state" "1.1.0" + "@radix-ui/react-use-layout-effect" "1.1.0" + "@radix-ui/react-use-previous" "1.1.0" + "@radix-ui/react-visually-hidden" "1.1.0" + "@radix-ui/react-popper@1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.2.0.tgz#a3e500193d144fe2d8f5d5e60e393d64111f2a7a" @@ -689,6 +709,11 @@ resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz#3c2c8ce04827b26a39e442ff4888d9212268bd27" integrity sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w== +"@radix-ui/react-use-previous@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-previous/-/react-use-previous-1.1.0.tgz#d4dd37b05520f1d996a384eb469320c2ada8377c" + integrity sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og== + "@radix-ui/react-use-rect@1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz#13b25b913bd3e3987cc9b073a1a164bb1cf47b88" @@ -703,6 +728,13 @@ dependencies: "@radix-ui/react-use-layout-effect" "1.1.0" +"@radix-ui/react-visually-hidden@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.0.tgz#ad47a8572580f7034b3807c8e6740cd41038a5a2" + integrity sha512-N8MDZqtgCgG5S3aV60INAB475osJousYpZ4cTJ2cFbMpdHS5Y6loLTH8LPtkj2QN0x93J30HT/M3qJXM0+lyeQ== + dependencies: + "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/rect@1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-1.1.0.tgz#f817d1d3265ac5415dadc67edab30ae196696438" @@ -743,6 +775,18 @@ dependencies: "@tanstack/query-core" "5.53.3" +"@tanstack/react-table@^8.20.5": + version "8.20.5" + resolved "https://registry.yarnpkg.com/@tanstack/react-table/-/react-table-8.20.5.tgz#19987d101e1ea25ef5406dce4352cab3932449d8" + integrity sha512-WEHopKw3znbUZ61s9i0+i9g8drmDo6asTWbrQh8Us63DAk/M0FkmIqERew6P71HI75ksZ2Pxyuf4vvKh9rAkiA== + dependencies: + "@tanstack/table-core" "8.20.5" + +"@tanstack/table-core@8.20.5": + version "8.20.5" + resolved "https://registry.yarnpkg.com/@tanstack/table-core/-/table-core-8.20.5.tgz#3974f0b090bed11243d4107283824167a395cf1d" + integrity sha512-P9dF7XbibHph2PFRz8gfBKEXEY/HJPOhym8CHmjF8y3q5mWpKx9xtZapXQUWCgkqvsK0R46Azuz+VaxD4Xl+Tg== + "@types/json5@^0.0.29": version "0.0.29" resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"