Persistent nodedb (#780)

* Refactor  and consolitdate store imports

- Created a new index file in the core stores directory to export all stores from a single module.
- Updated imports to use consolidated store exports.

* Remove unnecessary import

* Update imports

* First steps to persist nodeDB

* Use named exports

* Change store import after merge

* Persistent nodeDB initial work

* Key mishmatch warning, new serialization handler

* Minor copilot changes

* Add NODEDB_RETENTION_NUM

* Updated tests

* Refactor PKI mismatch logic

* Clear persisted db on reset

* Only persist on featureFlag

* Mock featureFlag in tests

---------

Co-authored-by: philon- <philon-@users.noreply.github.com>
This commit is contained in:
Jeremy Gallant
2025-08-24 09:23:20 -04:00
committed by GitHub
co-authored by philon-
parent e33463b371
commit 68ec7ee5d8
43 changed files with 1335 additions and 476 deletions
@@ -1,6 +1,11 @@
import { Mono } from "@components/generic/Mono.tsx";
import { Button } from "@components/UI/Button.tsx";
import { useAppStore, useDeviceStore, useMessageStore } from "@core/stores";
import {
useAppStore,
useDeviceStore,
useMessageStore,
useNodeDBStore,
} from "@core/stores";
import { subscribeAll } from "@core/subscriptions.ts";
import { randId } from "@core/utils/randId.ts";
import { MeshDevice } from "@meshtastic/core";
@@ -12,7 +17,9 @@ import type { TabElementProps } from "../../Dialog/NewDeviceDialog.tsx";
export const BLE = ({ closeDialog }: TabElementProps) => {
const [connectionInProgress, setConnectionInProgress] = useState(false);
const [bleDevices, setBleDevices] = useState<BluetoothDevice[]>([]);
const { addDevice } = useDeviceStore();
const { addNodeDB } = useNodeDBStore();
const messageStore = useMessageStore();
const { setSelectedDevice } = useAppStore();
const { t } = useTranslation();
@@ -29,11 +36,13 @@ export const BLE = ({ closeDialog }: TabElementProps) => {
const id = randId();
const transport = await TransportWebBluetooth.createFromDevice(bleDevice);
const device = addDevice(id);
const nodeDB = addNodeDB(id);
const connection = new MeshDevice(transport, id);
connection.configure();
setSelectedDevice(id);
device.addConnection(connection);
subscribeAll(device, connection, messageStore);
subscribeAll(device, connection, messageStore, nodeDB);
const HEARTBEAT_INTERVAL = 5 * 60 * 1000;
connection.setHeartbeatInterval(HEARTBEAT_INTERVAL);
@@ -9,7 +9,10 @@ vi.mock("@core/stores", () => ({
useDeviceStore: vi.fn(() => ({
addDevice: vi.fn(() => ({ addConnection: vi.fn() })),
})),
useMessageStore: vi.fn()
useMessageStore: vi.fn(),
useNodeDBStore: vi.fn(() => ({
addNodeDB: vi.fn(),
})),
}));
vi.mock("@core/utils/randId.ts", () => ({
@@ -4,7 +4,12 @@ import { Input } from "@components/UI/Input.tsx";
import { Label } from "@components/UI/Label.tsx";
import { Switch } from "@components/UI/Switch.tsx";
import { Link } from "@components/UI/Typography/Link.tsx";
import { useAppStore, useDeviceStore, useMessageStore } from "@core/stores";
import {
useAppStore,
useDeviceStore,
useMessageStore,
useNodeDBStore,
} from "@core/stores";
import { subscribeAll } from "@core/subscriptions.ts";
import { randId } from "@core/utils/randId.ts";
import { MeshDevice } from "@meshtastic/core";
@@ -25,6 +30,7 @@ export const HTTP = ({ closeDialog }: TabElementProps) => {
const isURLHTTPS = location.protocol === "https:";
const { addDevice } = useDeviceStore();
const { addNodeDB } = useNodeDBStore();
const messageStore = useMessageStore();
const { setSelectedDevice } = useAppStore();
@@ -56,11 +62,13 @@ export const HTTP = ({ closeDialog }: TabElementProps) => {
const id = randId();
const transport = await TransportHTTP.create(data.ip, data.tls);
const device = addDevice(id);
const nodeDB = addNodeDB(id);
const connection = new MeshDevice(transport, id);
connection.configure();
setSelectedDevice(id);
device.addConnection(connection);
subscribeAll(device, connection, messageStore);
subscribeAll(device, connection, messageStore, nodeDB);
closeDialog();
} catch (error) {
if (error instanceof Error) {
@@ -1,6 +1,11 @@
import { Mono } from "@components/generic/Mono.tsx";
import { Button } from "@components/UI/Button.tsx";
import { useAppStore, useDeviceStore, useMessageStore } from "@core/stores";
import {
useAppStore,
useDeviceStore,
useMessageStore,
useNodeDBStore,
} from "@core/stores";
import { subscribeAll } from "@core/subscriptions.ts";
import { randId } from "@core/utils/randId.ts";
import { MeshDevice } from "@meshtastic/core";
@@ -12,7 +17,9 @@ import type { TabElementProps } from "../../Dialog/NewDeviceDialog.tsx";
export const Serial = ({ closeDialog }: TabElementProps) => {
const [connectionInProgress, setConnectionInProgress] = useState(false);
const [serialPorts, setSerialPorts] = useState<SerialPort[]>([]);
const { addDevice } = useDeviceStore();
const { addNodeDB } = useNodeDBStore();
const messageStore = useMessageStore();
const { setSelectedDevice } = useAppStore();
const { t } = useTranslation();
@@ -34,12 +41,14 @@ export const Serial = ({ closeDialog }: TabElementProps) => {
const onConnect = async (port: SerialPort) => {
const id = randId();
const device = addDevice(id);
const nodeDB = addNodeDB(id);
setSelectedDevice(id);
const transport = await TransportWebSerial.createFromPort(port);
const connection = new MeshDevice(transport, id);
connection.configure();
device.addConnection(connection);
subscribeAll(device, connection, messageStore);
subscribeAll(device, connection, messageStore, nodeDB);
const HEARTBEAT_INTERVAL = 5 * 60 * 1000;
connection.setHeartbeatInterval(HEARTBEAT_INTERVAL);
@@ -6,7 +6,12 @@ import {
TooltipProvider,
TooltipTrigger,
} from "@components/UI/Tooltip.tsx";
import { MessageState, useDevice, useMessageStore } from "@core/stores";
import {
MessageState,
useDevice,
useMessageStore,
useNodeDB,
} from "@core/stores";
import type { Message } from "@core/stores/messageStore/types.ts";
import { cn } from "@core/utils/cn.ts";
import { type Protobuf, Types } from "@meshtastic/core";
@@ -47,7 +52,8 @@ interface MessageItemProps {
}
export const MessageItem = ({ message }: MessageItemProps) => {
const { config, getNode } = useDevice();
const { config } = useDevice();
const { getNode } = useNodeDB();
const { getMyNodeNum } = useMessageStore();
const { t, i18n } = useTranslation("messages");
@@ -1,6 +1,6 @@
import { TraceRoute } from "@components/PageComponents/Messages/TraceRoute.tsx";
import { mockDeviceStore } from "@core/stores/deviceStore/deviceStore.mock.ts";
import { useDevice } from "@core/stores";
import { mockNodeDBStore } from "@core/stores/nodeDBStore/nodeDBStore.mock.ts";
import { useNodeDB } from "@core/stores";
import { Protobuf } from "@meshtastic/core";
import { render, screen } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
@@ -64,9 +64,10 @@ describe("TraceRoute", () => {
]);
beforeEach(() => {
vi.resetAllMocks();
vi.mocked(useDevice).mockReturnValue({
...mockDeviceStore,
vi.mocked(useNodeDB).mockReturnValue({
...mockNodeDBStore,
getNode: (nodeNum: number): Protobuf.Mesh.NodeInfo | undefined => {
return mockNodes.get(nodeNum);
},
@@ -1,4 +1,4 @@
import { useDevice } from "@core/stores";
import { useNodeDB } from "@core/stores";
import type { Protobuf } from "@meshtastic/core";
import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
import { useTranslation } from "react-i18next";
@@ -23,7 +23,7 @@ interface RoutePathProps {
}
const RoutePath = ({ title, from, to, path, snr }: RoutePathProps) => {
const { getNode } = useDevice();
const { getNode } = useNodeDB();
const { t } = useTranslation();
return (