api-client/packages/hoppscotch-selfhost-web/src/kernel/store.ts

164 lines
4.6 KiB
TypeScript

import type {
StorageOptions,
StoreError,
StoreEvents,
StoreEventEmitter,
} from "@hoppscotch/kernel"
import * as E from "fp-ts/Either"
import { getModule } from "."
import { getKernelMode } from "@hoppscotch/kernel"
const STORE_PATH = "hoppscotch-unified.store"
let cachedStorePath: string | undefined
// These are only defined functions if in desktop mode.
// For more context, take a look at how `hoppscotch-kernel/.../store/v1/` works
// and how the `web` mode store kernel ignores the first file directory input.
let invoke:
| (<T>(cmd: string, args?: Record<string, unknown>) => Promise<T>)
| undefined
let join: ((...paths: string[]) => Promise<string>) | undefined
// Single init promise to avoid multiple imports and race conditions
let initPromise: Promise<void> | undefined
const isInitd = async () => {
if (getKernelMode() !== "desktop") return
if (!initPromise) {
initPromise = Promise.all([
import("@tauri-apps/api/core").then((module) => {
invoke = module.invoke
}),
import("@tauri-apps/api/path").then((module) => {
join = module.join
}),
]).then(() => {})
}
await initPromise
}
export const getConfigDir = async (): Promise<string> => {
await isInitd()
if (!invoke) throw new Error("getConfigDir is only available in desktop mode")
return await invoke<string>("get_config_dir")
}
export const getBackupDir = async (): Promise<string> => {
await isInitd()
if (!invoke) throw new Error("getBackupDir is only available in desktop mode")
return await invoke<string>("get_backup_dir")
}
export const getLatestDir = async (): Promise<string> => {
await isInitd()
if (!invoke) throw new Error("getLatestDir is only available in desktop mode")
return await invoke<string>("get_latest_dir")
}
export const getStoreDir = async (): Promise<string> => {
await isInitd()
if (!invoke) throw new Error("getStoreDir is only available in desktop mode")
return await invoke<string>("get_store_dir")
}
export const getInstanceDir = async (): Promise<string> => {
await isInitd()
if (!invoke)
throw new Error("getInstanceDir is only available in desktop mode")
return await invoke<string>("get_instance_dir")
}
const getStorePath = async (): Promise<string> => {
if (cachedStorePath) return cachedStorePath
if (getKernelMode() === "desktop") {
await isInitd()
if (join) {
try {
const storeDir = await getStoreDir()
cachedStorePath = await join(storeDir, STORE_PATH)
return cachedStorePath
} catch (error) {
console.error("Failed to get store directory:", error)
}
}
}
cachedStorePath = STORE_PATH
return cachedStorePath
}
export const Store = (() => {
const module = () => getModule("store")
return {
capabilities: () => module().capabilities,
init: async () => {
const storePath = await getStorePath()
return module().init(storePath)
},
set: async (
namespace: string,
key: string,
value: unknown,
options?: StorageOptions
): Promise<E.Either<StoreError, void>> => {
const storePath = await getStorePath()
return module().set(storePath, namespace, key, value, options)
},
get: async <T>(
namespace: string,
key: string
): Promise<E.Either<StoreError, T | undefined>> => {
const storePath = await getStorePath()
return module().get<T>(storePath, namespace, key)
},
remove: async (
namespace: string,
key: string
): Promise<E.Either<StoreError, boolean>> => {
const storePath = await getStorePath()
return module().remove(storePath, namespace, key)
},
clear: async (namespace?: string): Promise<E.Either<StoreError, void>> => {
const storePath = await getStorePath()
return module().clear(storePath, namespace)
},
has: async (
namespace: string,
key: string
): Promise<E.Either<StoreError, boolean>> => {
const storePath = await getStorePath()
return module().has(storePath, namespace, key)
},
listNamespaces: async (): Promise<E.Either<StoreError, string[]>> => {
const storePath = await getStorePath()
return module().listNamespaces(storePath)
},
listKeys: async (
namespace: string
): Promise<E.Either<StoreError, string[]>> => {
const storePath = await getStorePath()
return module().listKeys(storePath, namespace)
},
watch: async (
namespace: string,
key: string
): Promise<StoreEventEmitter<StoreEvents>> => {
const storePath = await getStorePath()
return module().watch(storePath, namespace, key)
},
} as const
})()