-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathusePluginData.ts
More file actions
85 lines (79 loc) · 2.77 KB
/
Copy pathusePluginData.ts
File metadata and controls
85 lines (79 loc) · 2.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { Get, Set } from '../../bindings/github.com/omniviewdev/omniview/backend/pkg/plugin/data/servicewrapper';
import { useResolvedPluginId } from '../useResolvedPluginId';
type UsePluginDataResult<T> = {
data: T;
update: (value: T) => Promise<void>;
isLoading: boolean;
};
/**
* Check whether a value from the data store structurally matches the expected
* type indicated by the default value. This catches cases where the Go backend
* returns a JSON type that doesn't match the TypeScript generic (e.g. an object
* was stored but the caller expects an array).
*/
function matchesShape<T>(value: unknown, defaultValue: T): value is T {
if (defaultValue === null) return value === null;
if (Array.isArray(defaultValue)) return Array.isArray(value);
if (typeof defaultValue === 'object') {
return typeof value === 'object' && value !== null && !Array.isArray(value);
}
return typeof value === typeof defaultValue;
}
/**
* Generic hook for reading/writing plugin data from the Plugin Data Store.
* Uses React Query for caching and optimistic updates.
*/
export function usePluginData<T>(
explicitPluginID: string | undefined,
key: string,
defaultValue: T,
): UsePluginDataResult<T> {
const pluginID = useResolvedPluginId(explicitPluginID);
const queryClient = useQueryClient();
const queryKey = [pluginID, 'data', key];
const query = useQuery({
queryKey,
queryFn: async () => {
const result = await Get(pluginID, key);
if (result === null || result === undefined) {
return defaultValue;
}
if (!matchesShape(result, defaultValue)) {
console.warn(
`[usePluginData] stored value for "${key}" has unexpected type ` +
`(expected ${Array.isArray(defaultValue) ? 'array' : typeof defaultValue}, ` +
`got ${Array.isArray(result) ? 'array' : typeof result}). Using default.`,
);
return defaultValue;
}
return result as T;
},
});
const mutation = useMutation({
mutationFn: async (value: T) => {
await Set(pluginID, key, value);
},
onMutate: async (value: T) => {
await queryClient.cancelQueries({ queryKey });
const previous = queryClient.getQueryData<T>(queryKey);
queryClient.setQueryData(queryKey, value);
return { previous };
},
onError: (_err, _value, context) => {
if (context?.previous !== undefined) {
queryClient.setQueryData(queryKey, context.previous);
}
},
onSettled: () => {
queryClient.invalidateQueries({ queryKey });
},
});
return {
data: query.data ?? defaultValue,
update: async (value: T) => {
await mutation.mutateAsync(value);
},
isLoading: query.isLoading,
};
}