@arcanejs/react-toolkit 0.1.5 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/data.d.mts +96 -0
- package/dist/data.d.ts +96 -0
- package/dist/data.js +248 -0
- package/dist/data.mjs +225 -0
- package/dist/index.d.mts +35 -21
- package/dist/index.d.ts +35 -21
- package/dist/index.js +35 -20664
- package/dist/index.mjs +37 -20695
- package/package.json +19 -2
package/dist/data.d.mts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { ReactNode, FC, Context } from 'react';
|
|
2
|
+
import { ZodType } from 'zod';
|
|
3
|
+
|
|
4
|
+
type WithPathChange = {
|
|
5
|
+
/**
|
|
6
|
+
* When the file path changes and the file does not yet exist,
|
|
7
|
+
* should the previous data be stored in the new file
|
|
8
|
+
* or should the new file be reset to the default value?
|
|
9
|
+
*
|
|
10
|
+
* @default 'defaultValue'
|
|
11
|
+
*/
|
|
12
|
+
onPathChange?: 'transfer' | 'defaultValue';
|
|
13
|
+
};
|
|
14
|
+
type DataFileUsage = WithPathChange & {
|
|
15
|
+
/**
|
|
16
|
+
* The path to where the JSON data should be stored,
|
|
17
|
+
* this will be relative to the current working directory.
|
|
18
|
+
*/
|
|
19
|
+
path: string;
|
|
20
|
+
};
|
|
21
|
+
type ProviderProps = DataFileUsage & {
|
|
22
|
+
children: ReactNode;
|
|
23
|
+
};
|
|
24
|
+
type DataFileUpdater<T> = (update: (current: T) => T) => void;
|
|
25
|
+
type DataFileContext<T> = {
|
|
26
|
+
data: T;
|
|
27
|
+
updateData: DataFileUpdater<T>;
|
|
28
|
+
/**
|
|
29
|
+
* Can be called to force an attempt to re-save the data to disk
|
|
30
|
+
*/
|
|
31
|
+
saveData: () => void;
|
|
32
|
+
/**
|
|
33
|
+
* If an error has ocurred in the last operation (e.g. load or save,
|
|
34
|
+
* then this will be set to that error).
|
|
35
|
+
*
|
|
36
|
+
* Can be used for example to re-prompt users to save data.
|
|
37
|
+
*/
|
|
38
|
+
error: unknown;
|
|
39
|
+
};
|
|
40
|
+
type DataFileDefinition<T> = {
|
|
41
|
+
Provider: FC<ProviderProps>;
|
|
42
|
+
context: Context<DataFileContext<T>>;
|
|
43
|
+
useDataFile: (props: DataFileUsage) => DataFileCore<T>;
|
|
44
|
+
};
|
|
45
|
+
declare function useDataFileData<T>(dataFile: DataFileDefinition<T>): T;
|
|
46
|
+
declare function useDataFileUpdater<T>(dataFile: DataFileDefinition<T>): DataFileUpdater<T>;
|
|
47
|
+
/**
|
|
48
|
+
* Convenience hook to use the internal data-file context hook
|
|
49
|
+
*/
|
|
50
|
+
declare function useDataFileContext<T>(dataFile: DataFileDefinition<T>): DataFileContext<T>;
|
|
51
|
+
/**
|
|
52
|
+
* Convenience hook to use the internal data-file definition hook
|
|
53
|
+
* in a more react-like manner.
|
|
54
|
+
*/
|
|
55
|
+
declare function useDataFile<T>(dataFile: DataFileDefinition<T>, usage: DataFileUsage): DataFileCore<T>;
|
|
56
|
+
type DataState<T> = {
|
|
57
|
+
status: 'loading';
|
|
58
|
+
/**
|
|
59
|
+
* The data is not yet loaded, so this will be undefined,
|
|
60
|
+
* but it's listed here as a property so that the data property can be
|
|
61
|
+
* directly used without a type-guard that uses `state`.
|
|
62
|
+
*
|
|
63
|
+
* This should hopefully also avoid situations where users may check for
|
|
64
|
+
* `state === 'ready'` instead of `state !== 'loading'`,
|
|
65
|
+
* and accidentally avoid displaying data that's available but unsaved.
|
|
66
|
+
*/
|
|
67
|
+
data: undefined;
|
|
68
|
+
} | {
|
|
69
|
+
status: 'error';
|
|
70
|
+
data: T | undefined;
|
|
71
|
+
error: unknown;
|
|
72
|
+
} | {
|
|
73
|
+
status: 'ready';
|
|
74
|
+
data: T;
|
|
75
|
+
};
|
|
76
|
+
type UseDataFileCoreProps<T> = WithPathChange & {
|
|
77
|
+
schema: ZodType<T>;
|
|
78
|
+
defaultValue: T;
|
|
79
|
+
path: string;
|
|
80
|
+
};
|
|
81
|
+
type DataFileCore<T> = {
|
|
82
|
+
data: DataState<T>;
|
|
83
|
+
updateData: DataFileUpdater<T>;
|
|
84
|
+
saveData: () => void;
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Primary hook for & logic for using data files.
|
|
88
|
+
*/
|
|
89
|
+
declare function useDataFileCore<T>({ schema, defaultValue, path, onPathChange, }: UseDataFileCoreProps<T>): DataFileCore<T>;
|
|
90
|
+
type CreateDataFileDefinitionProps<T> = {
|
|
91
|
+
schema: ZodType<T>;
|
|
92
|
+
defaultValue: T;
|
|
93
|
+
};
|
|
94
|
+
declare function createDataFileDefinition<T>({ schema, defaultValue, }: CreateDataFileDefinitionProps<T>): DataFileDefinition<T>;
|
|
95
|
+
|
|
96
|
+
export { type CreateDataFileDefinitionProps, type DataFileContext, type DataFileCore, type DataFileDefinition, type DataFileUpdater, type DataState, type ProviderProps, type UseDataFileCoreProps, createDataFileDefinition, useDataFile, useDataFileContext, useDataFileCore, useDataFileData, useDataFileUpdater };
|
package/dist/data.d.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { ReactNode, FC, Context } from 'react';
|
|
2
|
+
import { ZodType } from 'zod';
|
|
3
|
+
|
|
4
|
+
type WithPathChange = {
|
|
5
|
+
/**
|
|
6
|
+
* When the file path changes and the file does not yet exist,
|
|
7
|
+
* should the previous data be stored in the new file
|
|
8
|
+
* or should the new file be reset to the default value?
|
|
9
|
+
*
|
|
10
|
+
* @default 'defaultValue'
|
|
11
|
+
*/
|
|
12
|
+
onPathChange?: 'transfer' | 'defaultValue';
|
|
13
|
+
};
|
|
14
|
+
type DataFileUsage = WithPathChange & {
|
|
15
|
+
/**
|
|
16
|
+
* The path to where the JSON data should be stored,
|
|
17
|
+
* this will be relative to the current working directory.
|
|
18
|
+
*/
|
|
19
|
+
path: string;
|
|
20
|
+
};
|
|
21
|
+
type ProviderProps = DataFileUsage & {
|
|
22
|
+
children: ReactNode;
|
|
23
|
+
};
|
|
24
|
+
type DataFileUpdater<T> = (update: (current: T) => T) => void;
|
|
25
|
+
type DataFileContext<T> = {
|
|
26
|
+
data: T;
|
|
27
|
+
updateData: DataFileUpdater<T>;
|
|
28
|
+
/**
|
|
29
|
+
* Can be called to force an attempt to re-save the data to disk
|
|
30
|
+
*/
|
|
31
|
+
saveData: () => void;
|
|
32
|
+
/**
|
|
33
|
+
* If an error has ocurred in the last operation (e.g. load or save,
|
|
34
|
+
* then this will be set to that error).
|
|
35
|
+
*
|
|
36
|
+
* Can be used for example to re-prompt users to save data.
|
|
37
|
+
*/
|
|
38
|
+
error: unknown;
|
|
39
|
+
};
|
|
40
|
+
type DataFileDefinition<T> = {
|
|
41
|
+
Provider: FC<ProviderProps>;
|
|
42
|
+
context: Context<DataFileContext<T>>;
|
|
43
|
+
useDataFile: (props: DataFileUsage) => DataFileCore<T>;
|
|
44
|
+
};
|
|
45
|
+
declare function useDataFileData<T>(dataFile: DataFileDefinition<T>): T;
|
|
46
|
+
declare function useDataFileUpdater<T>(dataFile: DataFileDefinition<T>): DataFileUpdater<T>;
|
|
47
|
+
/**
|
|
48
|
+
* Convenience hook to use the internal data-file context hook
|
|
49
|
+
*/
|
|
50
|
+
declare function useDataFileContext<T>(dataFile: DataFileDefinition<T>): DataFileContext<T>;
|
|
51
|
+
/**
|
|
52
|
+
* Convenience hook to use the internal data-file definition hook
|
|
53
|
+
* in a more react-like manner.
|
|
54
|
+
*/
|
|
55
|
+
declare function useDataFile<T>(dataFile: DataFileDefinition<T>, usage: DataFileUsage): DataFileCore<T>;
|
|
56
|
+
type DataState<T> = {
|
|
57
|
+
status: 'loading';
|
|
58
|
+
/**
|
|
59
|
+
* The data is not yet loaded, so this will be undefined,
|
|
60
|
+
* but it's listed here as a property so that the data property can be
|
|
61
|
+
* directly used without a type-guard that uses `state`.
|
|
62
|
+
*
|
|
63
|
+
* This should hopefully also avoid situations where users may check for
|
|
64
|
+
* `state === 'ready'` instead of `state !== 'loading'`,
|
|
65
|
+
* and accidentally avoid displaying data that's available but unsaved.
|
|
66
|
+
*/
|
|
67
|
+
data: undefined;
|
|
68
|
+
} | {
|
|
69
|
+
status: 'error';
|
|
70
|
+
data: T | undefined;
|
|
71
|
+
error: unknown;
|
|
72
|
+
} | {
|
|
73
|
+
status: 'ready';
|
|
74
|
+
data: T;
|
|
75
|
+
};
|
|
76
|
+
type UseDataFileCoreProps<T> = WithPathChange & {
|
|
77
|
+
schema: ZodType<T>;
|
|
78
|
+
defaultValue: T;
|
|
79
|
+
path: string;
|
|
80
|
+
};
|
|
81
|
+
type DataFileCore<T> = {
|
|
82
|
+
data: DataState<T>;
|
|
83
|
+
updateData: DataFileUpdater<T>;
|
|
84
|
+
saveData: () => void;
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Primary hook for & logic for using data files.
|
|
88
|
+
*/
|
|
89
|
+
declare function useDataFileCore<T>({ schema, defaultValue, path, onPathChange, }: UseDataFileCoreProps<T>): DataFileCore<T>;
|
|
90
|
+
type CreateDataFileDefinitionProps<T> = {
|
|
91
|
+
schema: ZodType<T>;
|
|
92
|
+
defaultValue: T;
|
|
93
|
+
};
|
|
94
|
+
declare function createDataFileDefinition<T>({ schema, defaultValue, }: CreateDataFileDefinitionProps<T>): DataFileDefinition<T>;
|
|
95
|
+
|
|
96
|
+
export { type CreateDataFileDefinitionProps, type DataFileContext, type DataFileCore, type DataFileDefinition, type DataFileUpdater, type DataState, type ProviderProps, type UseDataFileCoreProps, createDataFileDefinition, useDataFile, useDataFileContext, useDataFileCore, useDataFileData, useDataFileUpdater };
|
package/dist/data.js
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/data.tsx
|
|
21
|
+
var data_exports = {};
|
|
22
|
+
__export(data_exports, {
|
|
23
|
+
createDataFileDefinition: () => createDataFileDefinition,
|
|
24
|
+
useDataFile: () => useDataFile,
|
|
25
|
+
useDataFileContext: () => useDataFileContext,
|
|
26
|
+
useDataFileCore: () => useDataFileCore,
|
|
27
|
+
useDataFileData: () => useDataFileData,
|
|
28
|
+
useDataFileUpdater: () => useDataFileUpdater
|
|
29
|
+
});
|
|
30
|
+
module.exports = __toCommonJS(data_exports);
|
|
31
|
+
var import_fs = require("fs");
|
|
32
|
+
var import_react = require("react");
|
|
33
|
+
var import_lodash = require("lodash");
|
|
34
|
+
var import_path = require("path");
|
|
35
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
36
|
+
function useDataFileData(dataFile) {
|
|
37
|
+
return (0, import_react.useContext)(dataFile.context).data;
|
|
38
|
+
}
|
|
39
|
+
function useDataFileUpdater(dataFile) {
|
|
40
|
+
return (0, import_react.useContext)(dataFile.context).updateData;
|
|
41
|
+
}
|
|
42
|
+
function useDataFileContext(dataFile) {
|
|
43
|
+
return (0, import_react.useContext)(dataFile.context);
|
|
44
|
+
}
|
|
45
|
+
function useDataFile(dataFile, usage) {
|
|
46
|
+
return dataFile.useDataFile(usage);
|
|
47
|
+
}
|
|
48
|
+
function useDataFileCore({
|
|
49
|
+
schema,
|
|
50
|
+
defaultValue,
|
|
51
|
+
path,
|
|
52
|
+
onPathChange = "defaultValue"
|
|
53
|
+
}) {
|
|
54
|
+
const state = (0, import_react.useRef)({
|
|
55
|
+
initialized: false,
|
|
56
|
+
path: null,
|
|
57
|
+
data: void 0,
|
|
58
|
+
previousData: void 0,
|
|
59
|
+
state: {
|
|
60
|
+
state: "saved"
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
(0, import_react.useEffect)(() => {
|
|
64
|
+
if (!state.current.initialized) {
|
|
65
|
+
state.current.initialized = true;
|
|
66
|
+
} else {
|
|
67
|
+
throw new Error(
|
|
68
|
+
"Cannot change schema or defaultValue after initialization"
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
}, [schema, defaultValue]);
|
|
72
|
+
const [data, setData] = (0, import_react.useState)({
|
|
73
|
+
status: "loading",
|
|
74
|
+
data: void 0
|
|
75
|
+
});
|
|
76
|
+
const updateDataFromState = (0, import_react.useMemo)(
|
|
77
|
+
() => () => {
|
|
78
|
+
const data2 = state.current.data;
|
|
79
|
+
if (state.current.state.state === "error") {
|
|
80
|
+
setData({
|
|
81
|
+
status: "error",
|
|
82
|
+
error: state.current.state.error,
|
|
83
|
+
data: data2
|
|
84
|
+
});
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (data2 === void 0) {
|
|
88
|
+
setData({
|
|
89
|
+
status: "loading",
|
|
90
|
+
data: void 0
|
|
91
|
+
});
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
setData({
|
|
95
|
+
status: "ready",
|
|
96
|
+
data: data2
|
|
97
|
+
});
|
|
98
|
+
},
|
|
99
|
+
[]
|
|
100
|
+
);
|
|
101
|
+
const saveData = (0, import_react.useMemo)(
|
|
102
|
+
() => (0, import_lodash.throttle)(
|
|
103
|
+
async () => {
|
|
104
|
+
if (state.current.state.state === "saved") {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const currentPath = state.current.path;
|
|
108
|
+
const currentData = state.current.data;
|
|
109
|
+
if (!currentPath || currentData === void 0) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
const json = JSON.stringify(currentData, null, 2);
|
|
114
|
+
await import_fs.promises.mkdir((0, import_path.dirname)(currentPath), { recursive: true });
|
|
115
|
+
await import_fs.promises.writeFile(currentPath, json, "utf8");
|
|
116
|
+
if (state.current.path === currentPath && state.current.data === currentData) {
|
|
117
|
+
state.current.state = { state: "saved" };
|
|
118
|
+
}
|
|
119
|
+
} catch (error) {
|
|
120
|
+
if (state.current.path === currentPath && state.current.data === currentData) {
|
|
121
|
+
state.current.state = { state: "error", error };
|
|
122
|
+
updateDataFromState();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
500,
|
|
127
|
+
{
|
|
128
|
+
// Write leading so that we always write to disk quickly when
|
|
129
|
+
// only single things have changed
|
|
130
|
+
leading: true,
|
|
131
|
+
// Trailing is important otherwise we may lose data
|
|
132
|
+
trailing: true
|
|
133
|
+
}
|
|
134
|
+
),
|
|
135
|
+
[]
|
|
136
|
+
);
|
|
137
|
+
(0, import_react.useEffect)(() => {
|
|
138
|
+
state.current = {
|
|
139
|
+
...state.current,
|
|
140
|
+
path,
|
|
141
|
+
data: void 0,
|
|
142
|
+
previousData: state.current.data ?? state.current.previousData,
|
|
143
|
+
state: {
|
|
144
|
+
state: "saved"
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
import_fs.promises.readFile(path, "utf8").then((data2) => {
|
|
148
|
+
const parsedData = schema.parse(JSON.parse(data2));
|
|
149
|
+
if (state.current.path === path) {
|
|
150
|
+
state.current.data = parsedData;
|
|
151
|
+
state.current.state = { state: "saved" };
|
|
152
|
+
updateDataFromState();
|
|
153
|
+
}
|
|
154
|
+
}).catch((error) => {
|
|
155
|
+
if (state.current.path !== path) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (error.code === "ENOENT") {
|
|
159
|
+
console.log("Creating new file");
|
|
160
|
+
const initialData = onPathChange === "transfer" && state.current.previousData !== void 0 ? state.current.previousData : defaultValue;
|
|
161
|
+
state.current.data = initialData;
|
|
162
|
+
state.current.state = { state: "dirty" };
|
|
163
|
+
saveData();
|
|
164
|
+
updateDataFromState();
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
state.current.state = { state: "error", error };
|
|
168
|
+
updateDataFromState();
|
|
169
|
+
});
|
|
170
|
+
}, [path, onPathChange]);
|
|
171
|
+
const updateData = (0, import_react.useMemo)(
|
|
172
|
+
() => (update) => {
|
|
173
|
+
if (state.current.path !== path) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (state.current.data === void 0) {
|
|
177
|
+
throw new Error("Attempt to update data before it has been loaded");
|
|
178
|
+
}
|
|
179
|
+
state.current.data = update(state.current.data);
|
|
180
|
+
state.current.state = { state: "dirty" };
|
|
181
|
+
saveData();
|
|
182
|
+
updateDataFromState();
|
|
183
|
+
},
|
|
184
|
+
[path]
|
|
185
|
+
);
|
|
186
|
+
return {
|
|
187
|
+
data,
|
|
188
|
+
updateData,
|
|
189
|
+
saveData
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
function createDataFileDefinition({
|
|
193
|
+
schema,
|
|
194
|
+
defaultValue
|
|
195
|
+
}) {
|
|
196
|
+
const context = (0, import_react.createContext)({
|
|
197
|
+
data: defaultValue,
|
|
198
|
+
updateData: () => {
|
|
199
|
+
throw new Error("Data file provider not used");
|
|
200
|
+
},
|
|
201
|
+
saveData: () => {
|
|
202
|
+
throw new Error("Data file provider not used");
|
|
203
|
+
},
|
|
204
|
+
error: void 0
|
|
205
|
+
});
|
|
206
|
+
const useDataFile2 = ({
|
|
207
|
+
path,
|
|
208
|
+
onPathChange
|
|
209
|
+
}) => useDataFileCore({
|
|
210
|
+
schema,
|
|
211
|
+
defaultValue,
|
|
212
|
+
path,
|
|
213
|
+
onPathChange
|
|
214
|
+
});
|
|
215
|
+
const Provider = ({ path, onPathChange, children }) => {
|
|
216
|
+
const { data, updateData, saveData } = useDataFile2({
|
|
217
|
+
path,
|
|
218
|
+
onPathChange
|
|
219
|
+
});
|
|
220
|
+
const providedContext = (0, import_react.useMemo)(
|
|
221
|
+
() => ({
|
|
222
|
+
data: data.status !== "loading" && data.data !== void 0 ? data.data : defaultValue,
|
|
223
|
+
updateData,
|
|
224
|
+
saveData,
|
|
225
|
+
error: data.status === "error" ? data.error : void 0
|
|
226
|
+
}),
|
|
227
|
+
[data, updateData]
|
|
228
|
+
);
|
|
229
|
+
if (data.status === "loading") {
|
|
230
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: "Loading..." });
|
|
231
|
+
}
|
|
232
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(context.Provider, { value: providedContext, children });
|
|
233
|
+
};
|
|
234
|
+
return {
|
|
235
|
+
Provider,
|
|
236
|
+
context,
|
|
237
|
+
useDataFile: useDataFile2
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
241
|
+
0 && (module.exports = {
|
|
242
|
+
createDataFileDefinition,
|
|
243
|
+
useDataFile,
|
|
244
|
+
useDataFileContext,
|
|
245
|
+
useDataFileCore,
|
|
246
|
+
useDataFileData,
|
|
247
|
+
useDataFileUpdater
|
|
248
|
+
});
|
package/dist/data.mjs
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
// src/data.tsx
|
|
2
|
+
import { promises as fs } from "fs";
|
|
3
|
+
import {
|
|
4
|
+
useMemo,
|
|
5
|
+
useContext,
|
|
6
|
+
createContext,
|
|
7
|
+
useState,
|
|
8
|
+
useEffect,
|
|
9
|
+
useRef
|
|
10
|
+
} from "react";
|
|
11
|
+
import { throttle } from "lodash";
|
|
12
|
+
import { dirname } from "path";
|
|
13
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
|
14
|
+
function useDataFileData(dataFile) {
|
|
15
|
+
return useContext(dataFile.context).data;
|
|
16
|
+
}
|
|
17
|
+
function useDataFileUpdater(dataFile) {
|
|
18
|
+
return useContext(dataFile.context).updateData;
|
|
19
|
+
}
|
|
20
|
+
function useDataFileContext(dataFile) {
|
|
21
|
+
return useContext(dataFile.context);
|
|
22
|
+
}
|
|
23
|
+
function useDataFile(dataFile, usage) {
|
|
24
|
+
return dataFile.useDataFile(usage);
|
|
25
|
+
}
|
|
26
|
+
function useDataFileCore({
|
|
27
|
+
schema,
|
|
28
|
+
defaultValue,
|
|
29
|
+
path,
|
|
30
|
+
onPathChange = "defaultValue"
|
|
31
|
+
}) {
|
|
32
|
+
const state = useRef({
|
|
33
|
+
initialized: false,
|
|
34
|
+
path: null,
|
|
35
|
+
data: void 0,
|
|
36
|
+
previousData: void 0,
|
|
37
|
+
state: {
|
|
38
|
+
state: "saved"
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (!state.current.initialized) {
|
|
43
|
+
state.current.initialized = true;
|
|
44
|
+
} else {
|
|
45
|
+
throw new Error(
|
|
46
|
+
"Cannot change schema or defaultValue after initialization"
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
}, [schema, defaultValue]);
|
|
50
|
+
const [data, setData] = useState({
|
|
51
|
+
status: "loading",
|
|
52
|
+
data: void 0
|
|
53
|
+
});
|
|
54
|
+
const updateDataFromState = useMemo(
|
|
55
|
+
() => () => {
|
|
56
|
+
const data2 = state.current.data;
|
|
57
|
+
if (state.current.state.state === "error") {
|
|
58
|
+
setData({
|
|
59
|
+
status: "error",
|
|
60
|
+
error: state.current.state.error,
|
|
61
|
+
data: data2
|
|
62
|
+
});
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (data2 === void 0) {
|
|
66
|
+
setData({
|
|
67
|
+
status: "loading",
|
|
68
|
+
data: void 0
|
|
69
|
+
});
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
setData({
|
|
73
|
+
status: "ready",
|
|
74
|
+
data: data2
|
|
75
|
+
});
|
|
76
|
+
},
|
|
77
|
+
[]
|
|
78
|
+
);
|
|
79
|
+
const saveData = useMemo(
|
|
80
|
+
() => throttle(
|
|
81
|
+
async () => {
|
|
82
|
+
if (state.current.state.state === "saved") {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const currentPath = state.current.path;
|
|
86
|
+
const currentData = state.current.data;
|
|
87
|
+
if (!currentPath || currentData === void 0) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
const json = JSON.stringify(currentData, null, 2);
|
|
92
|
+
await fs.mkdir(dirname(currentPath), { recursive: true });
|
|
93
|
+
await fs.writeFile(currentPath, json, "utf8");
|
|
94
|
+
if (state.current.path === currentPath && state.current.data === currentData) {
|
|
95
|
+
state.current.state = { state: "saved" };
|
|
96
|
+
}
|
|
97
|
+
} catch (error) {
|
|
98
|
+
if (state.current.path === currentPath && state.current.data === currentData) {
|
|
99
|
+
state.current.state = { state: "error", error };
|
|
100
|
+
updateDataFromState();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
500,
|
|
105
|
+
{
|
|
106
|
+
// Write leading so that we always write to disk quickly when
|
|
107
|
+
// only single things have changed
|
|
108
|
+
leading: true,
|
|
109
|
+
// Trailing is important otherwise we may lose data
|
|
110
|
+
trailing: true
|
|
111
|
+
}
|
|
112
|
+
),
|
|
113
|
+
[]
|
|
114
|
+
);
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
state.current = {
|
|
117
|
+
...state.current,
|
|
118
|
+
path,
|
|
119
|
+
data: void 0,
|
|
120
|
+
previousData: state.current.data ?? state.current.previousData,
|
|
121
|
+
state: {
|
|
122
|
+
state: "saved"
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
fs.readFile(path, "utf8").then((data2) => {
|
|
126
|
+
const parsedData = schema.parse(JSON.parse(data2));
|
|
127
|
+
if (state.current.path === path) {
|
|
128
|
+
state.current.data = parsedData;
|
|
129
|
+
state.current.state = { state: "saved" };
|
|
130
|
+
updateDataFromState();
|
|
131
|
+
}
|
|
132
|
+
}).catch((error) => {
|
|
133
|
+
if (state.current.path !== path) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (error.code === "ENOENT") {
|
|
137
|
+
console.log("Creating new file");
|
|
138
|
+
const initialData = onPathChange === "transfer" && state.current.previousData !== void 0 ? state.current.previousData : defaultValue;
|
|
139
|
+
state.current.data = initialData;
|
|
140
|
+
state.current.state = { state: "dirty" };
|
|
141
|
+
saveData();
|
|
142
|
+
updateDataFromState();
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
state.current.state = { state: "error", error };
|
|
146
|
+
updateDataFromState();
|
|
147
|
+
});
|
|
148
|
+
}, [path, onPathChange]);
|
|
149
|
+
const updateData = useMemo(
|
|
150
|
+
() => (update) => {
|
|
151
|
+
if (state.current.path !== path) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if (state.current.data === void 0) {
|
|
155
|
+
throw new Error("Attempt to update data before it has been loaded");
|
|
156
|
+
}
|
|
157
|
+
state.current.data = update(state.current.data);
|
|
158
|
+
state.current.state = { state: "dirty" };
|
|
159
|
+
saveData();
|
|
160
|
+
updateDataFromState();
|
|
161
|
+
},
|
|
162
|
+
[path]
|
|
163
|
+
);
|
|
164
|
+
return {
|
|
165
|
+
data,
|
|
166
|
+
updateData,
|
|
167
|
+
saveData
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function createDataFileDefinition({
|
|
171
|
+
schema,
|
|
172
|
+
defaultValue
|
|
173
|
+
}) {
|
|
174
|
+
const context = createContext({
|
|
175
|
+
data: defaultValue,
|
|
176
|
+
updateData: () => {
|
|
177
|
+
throw new Error("Data file provider not used");
|
|
178
|
+
},
|
|
179
|
+
saveData: () => {
|
|
180
|
+
throw new Error("Data file provider not used");
|
|
181
|
+
},
|
|
182
|
+
error: void 0
|
|
183
|
+
});
|
|
184
|
+
const useDataFile2 = ({
|
|
185
|
+
path,
|
|
186
|
+
onPathChange
|
|
187
|
+
}) => useDataFileCore({
|
|
188
|
+
schema,
|
|
189
|
+
defaultValue,
|
|
190
|
+
path,
|
|
191
|
+
onPathChange
|
|
192
|
+
});
|
|
193
|
+
const Provider = ({ path, onPathChange, children }) => {
|
|
194
|
+
const { data, updateData, saveData } = useDataFile2({
|
|
195
|
+
path,
|
|
196
|
+
onPathChange
|
|
197
|
+
});
|
|
198
|
+
const providedContext = useMemo(
|
|
199
|
+
() => ({
|
|
200
|
+
data: data.status !== "loading" && data.data !== void 0 ? data.data : defaultValue,
|
|
201
|
+
updateData,
|
|
202
|
+
saveData,
|
|
203
|
+
error: data.status === "error" ? data.error : void 0
|
|
204
|
+
}),
|
|
205
|
+
[data, updateData]
|
|
206
|
+
);
|
|
207
|
+
if (data.status === "loading") {
|
|
208
|
+
return /* @__PURE__ */ jsx(Fragment, { children: "Loading..." });
|
|
209
|
+
}
|
|
210
|
+
return /* @__PURE__ */ jsx(context.Provider, { value: providedContext, children });
|
|
211
|
+
};
|
|
212
|
+
return {
|
|
213
|
+
Provider,
|
|
214
|
+
context,
|
|
215
|
+
useDataFile: useDataFile2
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
export {
|
|
219
|
+
createDataFileDefinition,
|
|
220
|
+
useDataFile,
|
|
221
|
+
useDataFileContext,
|
|
222
|
+
useDataFileCore,
|
|
223
|
+
useDataFileData,
|
|
224
|
+
useDataFileUpdater
|
|
225
|
+
};
|