Browse Source

First steps to persist nodeDB

pull/780/head
philon- 11 months ago
parent
commit
906773d382
  1. 177
      packages/web/src/core/stores/nodeDBStore/index.ts
  2. 6
      packages/web/src/core/stores/nodeDBStore/types.ts
  3. 80
      packages/web/src/core/stores/utils/indexDB.ts

177
packages/web/src/core/stores/nodeDBStore/index.ts

@ -0,0 +1,177 @@
import { createStorageWithMapSupport } from "@core/stores/utils/indexDB.ts";
import type { Protobuf } from "@meshtastic/core";
import { produce } from "immer";
import { create as createStore } from "zustand";
import { persist } from "zustand/middleware";
import type { NodeError } from "./types";
const CURRENT_STORE_VERSION = 0;
export interface nodeDB {
userId: number;
nodeMap: Map<number, Protobuf.Mesh.NodeInfo>;
nodeErrors: Map<number, NodeError>;
addNode: (nodeInfo: Protobuf.Mesh.NodeInfo) => void;
removeNode: (nodeNum: number) => void;
getNodesLength: () => number;
getNode: (nodeNum: number) => Protobuf.Mesh.NodeInfo | undefined;
getNodes: (
filter?: (node: Protobuf.Mesh.NodeInfo) => boolean,
) => Protobuf.Mesh.NodeInfo[];
setNodeError: (nodeNum: number, error: string) => void;
clearNodeError: (nodeNum: number) => void;
getNodeError: (nodeNum: number) => NodeError | undefined;
hasNodeError: (nodeNum: number) => boolean;
}
export interface nodeDBState {
addNodeDB: (id: number) => nodeDB;
removeNodeDB: (id: number) => void;
getNodeDBs: () => nodeDB[];
getNodeDB: (id: number) => nodeDB | undefined;
}
interface PrivateNodeDBState extends nodeDBState {
nodeDBs: Map<number, nodeDB>;
}
type NodeDBPersisted = Pick<PrivateNodeDBState, "nodeDBs">;
export const useNodeDBStore = createStore<PrivateNodeDBState>()(
persist(
(set, get) => ({
nodeDBs: new Map(),
addNodeDB: (userId: number) => {
set(
produce<PrivateNodeDBState>((draft) => {
draft.nodeDBs.set(userId, {
userId: userId,
nodeMap: new Map(),
nodeErrors: new Map(),
addNode: (node: Protobuf.Mesh.NodeInfo) =>
set(
produce((draft: PrivateNodeDBState) => {
const nodeDB = draft.nodeDBs.get(userId);
if (!nodeDB) {
throw new Error(`NodeDB for userID ${userId} not found`);
}
nodeDB.nodeMap.set(node.num, node);
}),
),
removeNode: (nodeNum: number) =>
set(
produce((draft: PrivateNodeDBState) => {
const nodeDB = draft.nodeDBs.get(userId);
if (!nodeDB) {
throw new Error(`NodeDB for userID ${userId} not found`);
}
nodeDB.nodeMap.delete(nodeNum);
}),
),
setNodeError: (nodeNum: number, error: string) =>
set(
produce((draft: PrivateNodeDBState) => {
const nodeDB = draft.nodeDBs.get(userId);
if (!nodeDB) {
throw new Error(`NodeDB for userID ${userId} not found`);
}
nodeDB.nodeErrors.set(nodeNum, { node: nodeNum, error });
}),
),
clearNodeError: (nodeNum: number) =>
set(
produce((draft: PrivateNodeDBState) => {
const nodeDB = draft.nodeDBs.get(userId);
if (!nodeDB) {
throw new Error(`NodeDB for userID ${userId} not found`);
}
nodeDB.nodeErrors.delete(nodeNum);
}),
),
getNodesLength: () => {
const nodeDB = draft.nodeDBs.get(userId);
if (!nodeDB) {
throw new Error(`NodeDB for userID ${userId} not found`);
}
return nodeDB.nodeMap.size;
},
getNode: (nodeNum: number) => {
const nodeDB = draft.nodeDBs.get(userId);
if (!nodeDB) {
throw new Error(`NodeDB for userID ${userId} not found`);
}
return nodeDB.nodeMap.get(nodeNum);
},
getNodes: (filter?) => {
const nodeDB = draft.nodeDBs.get(userId);
if (!nodeDB) {
throw new Error(`NodeDB for userID ${userId} not found`);
}
const allNodes = Array.from(nodeDB.nodeMap.values()).filter(
(node) => node.num !== nodeDB.userId,
);
if (filter) {
return allNodes.filter(filter);
}
return allNodes;
},
getNodeError: (nodeNum: number) => {
const nodeDB = draft.nodeDBs.get(userId);
if (!nodeDB) {
throw new Error(`NodeDB for userID ${userId} not found`);
}
return nodeDB.nodeErrors.get(nodeNum);
},
hasNodeError: (nodeNum: number) => {
const nodeDB = draft.nodeDBs.get(userId);
if (!nodeDB) {
throw new Error(`NodeDB for userID ${userId} not found`);
}
return nodeDB.nodeErrors.has(nodeNum) ?? false;
},
});
}),
);
const nodeDB = get().nodeDBs.get(userId);
if (!nodeDB) {
throw new Error(
`Failed to create or retrieve nodeDB for userID ${userId}`,
);
}
return nodeDB;
},
removeNodeDB: (userId) => {
set(
produce<PrivateNodeDBState>((draft) => {
draft.nodeDBs.delete(userId);
}),
);
},
getNodeDBs: () => Array.from(get().nodeDBs.values()),
getNodeDB: (userId) => get().nodeDBs.get(userId),
}),
{
name: "meshtastic-nodedb-store",
storage: createStorageWithMapSupport<NodeDBPersisted>(),
version: CURRENT_STORE_VERSION,
partialize: (s) => ({
nodeDBs: s.nodeDBs,
}),
},
),
);

6
packages/web/src/core/stores/nodeDBStore/types.ts

@ -0,0 +1,6 @@
type NodeError = {
node: number;
error: string;
};
export type { NodeError };

80
packages/web/src/core/stores/utils/indexDB.ts

@ -1,7 +1,3 @@
import type {
ChannelId,
MessageLogMap,
} from "@core/stores/messageStore/types.ts";
import { del, get, set } from "idb-keyval";
import type {
PersistStorage,
@ -9,14 +5,6 @@ import type {
StorageValue,
} from "zustand/middleware";
type PersistedMessageState = {
messages: {
direct: Map<string, MessageLogMap>;
broadcast: Map<ChannelId, MessageLogMap>;
};
nodeNum: number;
};
export const zustandIndexDBStorage: StateStorage = {
getItem: async (name: string): Promise<string | null> => {
return (await get(name)) || null;
@ -62,40 +50,34 @@ const reviver: JsonReviver = (_, value) => {
return value;
};
export const storageWithMapSupport: PersistStorage<PersistedMessageState> = {
getItem: async (
name,
): Promise<StorageValue<PersistedMessageState> | null> => {
const str = await zustandIndexDBStorage.getItem(name);
if (!str) {
return null;
}
try {
const parsed = JSON.parse(
str,
reviver,
) as StorageValue<PersistedMessageState>;
return parsed;
} catch (error) {
console.error(`Error parsing persisted state (${name}):`, error);
return null;
}
},
setItem: async (
name,
newValue: StorageValue<PersistedMessageState>,
): Promise<void> => {
try {
const str = JSON.stringify(newValue, replacer);
await zustandIndexDBStorage.setItem(name, str);
} catch (error) {
console.error(
`Error stringifying or setting persisted state (${name}):`,
error,
);
}
},
removeItem: async (name): Promise<void> => {
await zustandIndexDBStorage.removeItem(name);
},
};
export function createStorageWithMapSupport<T>(): PersistStorage<T> {
return {
getItem: async (name): Promise<StorageValue<T> | null> => {
const str = await zustandIndexDBStorage.getItem(name);
if (!str) {
return null;
}
try {
const parsed = JSON.parse(str, reviver) as StorageValue<T>;
return parsed;
} catch (error) {
console.error(`Error parsing persisted state (${name}):`, error);
return null;
}
},
setItem: async (name, newValue: StorageValue<T>): Promise<void> => {
try {
const str = JSON.stringify(newValue, replacer);
await zustandIndexDBStorage.setItem(name, str);
} catch (error) {
console.error(
`Error stringifying or setting persisted state (${name}):`,
error,
);
}
},
removeItem: async (name): Promise<void> => {
await zustandIndexDBStorage.removeItem(name);
},
};
}

Loading…
Cancel
Save