Skip to content

Commit f0c0b98

Browse files
authored
fix(explorer): fetch from sqlite indexer in production (#3679)
1 parent f6d87ed commit f0c0b98

22 files changed

Lines changed: 209 additions & 139 deletions

.changeset/forty-icons-eat.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@latticexyz/explorer": patch
3+
---
4+
5+
Fixed fetching data from `@latticexyz/store-indexer` `/q` API endpoint in production builds.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
"use client";
2+
3+
import { ReactNode } from "react";
4+
import "@rainbow-me/rainbowkit/styles.css";
5+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
6+
7+
const queryClient = new QueryClient();
8+
9+
export function Providers({ children }: { children: ReactNode }) {
10+
return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>;
11+
}

packages/explorer/src/app/(explorer)/[chainName]/layout.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { notFound } from "next/navigation";
44
import { isValidChainName } from "../../../common";
5+
import { Providers } from "./Providers";
56

67
type Props = {
78
params: {
@@ -15,5 +16,5 @@ export default function ChainLayout({ params: { chainName }, children }: Props)
1516
return notFound();
1617
}
1718

18-
return children;
19+
return <Providers>{children}</Providers>;
1920
}

packages/explorer/src/app/(explorer)/[chainName]/worlds/WorldsForm.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,12 @@ const formSchema = z.object({
2424
.transform((value): Address => value as Address),
2525
});
2626

27-
export function WorldsForm({ worlds }: { worlds: Address[] }) {
27+
type Props = {
28+
worlds: Address[];
29+
isLoading: boolean;
30+
};
31+
32+
export function WorldsForm({ worlds, isLoading }: Props) {
2833
const router = useRouter();
2934
const { chainName } = useParams();
3035
const [open, setOpen] = useState(false);
@@ -78,14 +83,15 @@ export function WorldsForm({ worlds }: { worlds: Address[] }) {
7883
setOpen(false);
7984
}}
8085
onFocus={handleOpenOptions}
81-
placeholder="Enter world address..."
86+
placeholder={isLoading ? "Loading worlds..." : "Enter world address..."}
8287
// Need to manually trigger form submission as CommandPrimitive.Input captures Enter key events
8388
onKeyDown={(e) => {
8489
if (!open && e.key === "Enter") {
8590
e.preventDefault();
8691
form.handleSubmit(onSubmit)();
8792
}
8893
}}
94+
disabled={isLoading}
8995
>
9096
<Input className="h-12" />
9197
</CommandPrimitive.Input>
@@ -130,14 +136,14 @@ export function WorldsForm({ worlds }: { worlds: Address[] }) {
130136
</div>
131137

132138
<div className="flex w-full items-center gap-x-2">
133-
<Button type="submit" className="flex-1 uppercase" variant="default">
139+
<Button type="submit" className="flex-1 uppercase" variant="default" disabled={isLoading}>
134140
Explore the world
135141
</Button>
136142
<Button
137143
className="flex-1 uppercase"
138144
variant="secondary"
139145
onClick={onLuckyWorld}
140-
disabled={worlds.length === 0}
146+
disabled={worlds.length === 0 || isLoading}
141147
>
142148
I&apos;m feeling lucky
143149
</Button>

packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/Explorer.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ import { useQueryState } from "nuqs";
55
import { Hex } from "viem";
66
import { useEffect, useState } from "react";
77
import { useChain } from "../../../../hooks/useChain";
8+
import { useIndexerForChainId } from "../../../../hooks/useIndexerForChainId";
89
import { usePrevious } from "../../../../hooks/usePrevious";
910
import { useTablesQuery } from "../../../../queries/useTablesQuery";
1011
import { constructTableName } from "../../../../utils/constructTableName";
11-
import { indexerForChainId } from "../../../../utils/indexerForChainId";
1212
import { SQLEditor } from "./SQLEditor";
1313
import { TableSelector } from "./TableSelector";
1414
import { TablesViewer } from "./TablesViewer";
@@ -18,7 +18,8 @@ import { useSQLQueryState } from "./hooks/useSQLQueryState";
1818
export function Explorer() {
1919
const { worldAddress } = useParams();
2020
const { id: chainId } = useChain();
21-
const indexer = indexerForChainId(chainId);
21+
const indexer = useIndexerForChainId(chainId);
22+
2223
const [isLiveQuery, setIsLiveQuery] = useState(indexer.type === "sqlite");
2324
const [{ pageSize }, setPagination] = usePaginationState();
2425
const [query, setQuery] = useSQLQueryState();
@@ -29,7 +30,7 @@ export function Explorer() {
2930

3031
useEffect(() => {
3132
if (table && (!query || prevSelectedTableId !== selectedTableId)) {
32-
const tableName = constructTableName(table, worldAddress as Hex, chainId);
33+
const tableName = constructTableName(table, worldAddress as Hex, indexer.type);
3334
if (indexer.type === "sqlite") {
3435
setQuery(`SELECT * FROM "${tableName}" WHERE __isDeleted = 0;`);
3536
} else {

packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/TableSelector.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
import { Popover, PopoverContent, PopoverTrigger } from "../../../../../../components/ui/Popover";
1717
import { cn } from "../../../../../../utils";
1818
import { useChain } from "../../../../hooks/useChain";
19+
import { useIndexerForChainId } from "../../../../hooks/useIndexerForChainId";
1920
import { constructTableName } from "../../../../utils/constructTableName";
2021

2122
function TableSelectorItem({ table, selected, asOption }: { table: Table; selected: boolean; asOption?: boolean }) {
@@ -33,6 +34,7 @@ function TableSelectorItem({ table, selected, asOption }: { table: Table; select
3334
export function TableSelector({ tables }: { tables?: Table[] }) {
3435
const { worldAddress } = useParams();
3536
const { id: chainId } = useChain();
37+
const indexer = useIndexerForChainId(chainId);
3638
const [selectedTableId, setTableId] = useQueryState("tableId");
3739
const [open, setOpen] = useState(false);
3840
const [searchValue, setSearchValue] = useState("");
@@ -47,12 +49,12 @@ export function TableSelector({ tables }: { tables?: Table[] }) {
4749
useLayoutEffect(() => {
4850
if (open && selectedTableId && selectedTableConfig) {
4951
setTimeout(() => {
50-
const selectedTableId = constructTableName(selectedTableConfig, worldAddress as Hex, chainId);
52+
const selectedTableId = constructTableName(selectedTableConfig, worldAddress as Hex, indexer.type);
5153
const selectedElement = document.querySelector(`[data-value="${selectedTableId}"]`);
5254
selectedElement?.scrollIntoView({ behavior: "instant", block: "center" });
5355
}, 0);
5456
}
55-
}, [chainId, open, selectedTableConfig, selectedTableId, worldAddress]);
57+
}, [chainId, indexer.type, open, selectedTableConfig, selectedTableId, worldAddress]);
5658

5759
return (
5860
<div className="w-full">
@@ -93,7 +95,7 @@ export function TableSelector({ tables }: { tables?: Table[] }) {
9395
return (
9496
<CommandItem
9597
key={table.tableId}
96-
value={constructTableName(table, worldAddress as Hex, chainId)}
98+
value={constructTableName(table, worldAddress as Hex, indexer.type)}
9799
onSelect={() => {
98100
setTableId(table.tableId);
99101
setOpen(false);

packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/TablesViewer.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from ".
3030
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../../../../../../components/ui/Table";
3131
import { cn } from "../../../../../../utils";
3232
import { useChain } from "../../../../hooks/useChain";
33+
import { useIndexerForChainId } from "../../../../hooks/useIndexerForChainId";
3334
import { TData, TDataRow, useTableDataQuery } from "../../../../queries/useTableDataQuery";
34-
import { indexerForChainId } from "../../../../utils/indexerForChainId";
3535
import { ExportButton } from "./ExportButton";
3636
import { PAGE_SIZE_OPTIONS } from "./consts";
3737
import { defaultColumn } from "./defaultColumn";
@@ -58,7 +58,7 @@ type Props = {
5858

5959
export function TablesViewer({ table, isLiveQuery }: Props) {
6060
const { id: chainId } = useChain();
61-
const indexer = indexerForChainId(chainId);
61+
const indexer = useIndexerForChainId(chainId);
6262
const [query, setQuery] = useSQLQueryState();
6363
const [globalFilter, setGlobalFilter] = useQueryState("filter", parseAsString.withDefault(""));
6464
const [sorting, setSorting] = useQueryState("sort", parseAsJson<SortingState>().withDefault(initialSortingState));

packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/useQueryAutocomplete.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,20 @@ import { Address } from "viem";
44
import { useMemo } from "react";
55
import { Table } from "@latticexyz/config";
66
import { useChain } from "../../../../hooks/useChain";
7+
import { useIndexerForChainId } from "../../../../hooks/useIndexerForChainId";
78
import { constructTableName } from "../../../../utils/constructTableName";
89

910
export function useQueryAutocomplete(table?: Table) {
10-
const { id: chainId } = useChain();
1111
const { worldAddress } = useParams<{ worldAddress: Address }>();
12+
const { id: chainId } = useChain();
13+
const indexer = useIndexerForChainId(chainId);
1214

1315
return useMemo(() => {
1416
if (!table || !worldAddress || !chainId) return null;
1517

16-
const tableName = constructTableName(table, worldAddress as Address, chainId);
18+
const tableName = constructTableName(table, worldAddress as Address, indexer.type);
1719
const columnNames = Object.keys(table.schema);
1820

1921
return new SQLAutocomplete(SQLDialect.PLpgSQL, [tableName], columnNames);
20-
}, [table, worldAddress, chainId]);
22+
}, [table, worldAddress, chainId, indexer.type]);
2123
}

packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/useQueryValidator.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { useCallback } from "react";
55
import { Table } from "@latticexyz/config";
66
import { useMonaco } from "@monaco-editor/react";
77
import { useChain } from "../../../../hooks/useChain";
8+
import { useIndexerForChainId } from "../../../../hooks/useIndexerForChainId";
89
import { constructTableName } from "../../../../utils/constructTableName";
910
import { useMonacoErrorMarker } from "./useMonacoErrorMarker";
1011

@@ -47,6 +48,7 @@ export function useQueryValidator(table?: Table) {
4748
const monaco = useMonaco();
4849
const { worldAddress } = useParams();
4950
const { id: chainId } = useChain();
51+
const indexer = useIndexerForChainId(chainId);
5052
const setErrorMarker = useMonacoErrorMarker();
5153

5254
return useCallback(
@@ -73,7 +75,7 @@ export function useQueryValidator(table?: Table) {
7375
for (const tableInfo of ast.from) {
7476
if ("table" in tableInfo) {
7577
const selectedTableName = tableInfo.table;
76-
const tableName = constructTableName(table, worldAddress as Address, chainId);
78+
const tableName = constructTableName(table, worldAddress as Address, indexer.type);
7779

7880
if (selectedTableName !== tableName) {
7981
setErrorMarker({
@@ -106,6 +108,6 @@ export function useQueryValidator(table?: Table) {
106108
return false;
107109
}
108110
},
109-
[monaco, table, setErrorMarker, worldAddress, chainId],
111+
[monaco, table, setErrorMarker, worldAddress, indexer.type],
110112
);
111113
}
Lines changed: 14 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,26 @@
1-
import { headers } from "next/headers";
1+
"use client";
2+
23
import { redirect } from "next/navigation";
3-
import { Address } from "viem";
4-
import { supportedChainName, supportedChains } from "../../../../common";
5-
import { indexerForChainId } from "../../utils/indexerForChainId";
4+
import { supportedChainName } from "../../../../common";
5+
import { useWorldsQuery } from "../../queries/useWorldsQuery";
66
import { WorldsForm } from "./WorldsForm";
77

8-
type ApiResponse = {
9-
items: {
10-
address: {
11-
hash: Address;
12-
};
13-
}[];
14-
};
15-
16-
async function fetchWorlds(chainName: supportedChainName): Promise<Address[]> {
17-
const chain = supportedChains[chainName];
18-
const indexer = indexerForChainId(chain.id);
19-
let worldsApiUrl: string | null = null;
20-
21-
if (indexer.type === "sqlite") {
22-
const headersList = headers();
23-
const host = headersList.get("host") || "";
24-
const protocol = headersList.get("x-forwarded-proto") || "http";
25-
const baseUrl = `${protocol}://${host}`;
26-
worldsApiUrl = `${baseUrl}/api/sqlite-indexer/worlds`;
27-
} else {
28-
const blockExplorerUrl = "blockExplorers" in chain && chain.blockExplorers?.default.url;
29-
if (blockExplorerUrl) {
30-
worldsApiUrl = `${blockExplorerUrl}/api/v2/mud/worlds`;
31-
}
32-
}
33-
34-
if (!worldsApiUrl) {
35-
return [];
36-
}
37-
38-
try {
39-
const response = await fetch(worldsApiUrl);
40-
const data: ApiResponse = await response.json();
41-
return data.items.map((world) => world.address.hash);
42-
} catch (error) {
43-
console.error(error);
44-
return [];
45-
}
46-
}
47-
488
type Props = {
499
params: {
5010
chainName: supportedChainName;
5111
};
5212
};
5313

54-
export default async function WorldsPage({ params: { chainName } }: Props) {
55-
const worlds = await fetchWorlds(chainName);
14+
export default function WorldsPage({ params: { chainName } }: Props) {
15+
const { data, isLoading, error } = useWorldsQuery();
16+
if (error) {
17+
throw error;
18+
}
19+
20+
const worlds = data?.worlds || [];
5621
if (worlds.length === 1) {
57-
return redirect(`/${chainName}/worlds/${worlds[0]}`);
22+
redirect(`/${chainName}/worlds/${worlds[0]}`);
5823
}
59-
return <WorldsForm worlds={worlds} />;
24+
25+
return <WorldsForm worlds={worlds} isLoading={isLoading} />;
6026
}

0 commit comments

Comments
 (0)