@arcanejs/react-toolkit 0.13.0 → 0.15.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 +18 -2
- package/dist/data.d.ts +18 -2
- package/dist/data.js +103 -26
- package/dist/data.mjs +101 -24
- package/package.json +1 -1
package/dist/data.d.mts
CHANGED
|
@@ -17,6 +17,8 @@ type DataFileUsage = WithPathChange & {
|
|
|
17
17
|
* this will be relative to the current working directory.
|
|
18
18
|
*/
|
|
19
19
|
path: string;
|
|
20
|
+
/** Callback that will be called if any error occurs during loading or saving the file */
|
|
21
|
+
onError?: (error: ArcaneDataFileError) => void;
|
|
20
22
|
};
|
|
21
23
|
type ProviderProps = DataFileUsage & {
|
|
22
24
|
children: ReactNode;
|
|
@@ -43,6 +45,10 @@ type DataFileContext<T> = {
|
|
|
43
45
|
*/
|
|
44
46
|
lastUpdatedMillis: number;
|
|
45
47
|
updateData: DataFileUpdater<T>;
|
|
48
|
+
/**
|
|
49
|
+
* Can be called to reset the data to the default value, and save that to disk.
|
|
50
|
+
*/
|
|
51
|
+
resetData: () => void;
|
|
46
52
|
/**
|
|
47
53
|
* Can be called to force an attempt to re-save the data to disk
|
|
48
54
|
*/
|
|
@@ -93,24 +99,34 @@ type DataState<T> = {
|
|
|
93
99
|
status: 'ready';
|
|
94
100
|
data: T;
|
|
95
101
|
});
|
|
102
|
+
type DataFileOperation = 'load' | 'save' | 'usage';
|
|
103
|
+
declare class ArcaneDataFileError extends Error {
|
|
104
|
+
readonly operation: DataFileOperation;
|
|
105
|
+
readonly path: string | null;
|
|
106
|
+
readonly contents: string | null;
|
|
107
|
+
constructor(message: string, operation: DataFileOperation, path: string | null, contents: string | null, cause?: unknown);
|
|
108
|
+
}
|
|
109
|
+
type ErrorListener = (error: ArcaneDataFileError) => void;
|
|
96
110
|
type UseDataFileCoreProps<T> = WithPathChange & {
|
|
97
111
|
schema: ZodType<T>;
|
|
98
112
|
defaultValue: T;
|
|
99
113
|
path: string;
|
|
114
|
+
onError?: (error: ArcaneDataFileError) => void;
|
|
100
115
|
};
|
|
101
116
|
type DataFileCore<T> = {
|
|
102
117
|
data: DataState<T>;
|
|
103
118
|
updateData: DataFileUpdater<T>;
|
|
119
|
+
resetData: () => void;
|
|
104
120
|
saveData: () => void;
|
|
105
121
|
};
|
|
106
122
|
/**
|
|
107
123
|
* Primary hook for & logic for using data files.
|
|
108
124
|
*/
|
|
109
|
-
declare function useDataFileCore<T>({ schema, defaultValue, path, onPathChange, }: UseDataFileCoreProps<T>): DataFileCore<T>;
|
|
125
|
+
declare function useDataFileCore<T>({ schema, defaultValue, path, onPathChange, onError, }: UseDataFileCoreProps<T>): DataFileCore<T>;
|
|
110
126
|
type CreateDataFileDefinitionProps<T extends ZodType> = {
|
|
111
127
|
schema: T;
|
|
112
128
|
defaultValue: z.infer<T>;
|
|
113
129
|
};
|
|
114
130
|
declare function createDataFileDefinition<T extends ZodType>({ schema, defaultValue, }: CreateDataFileDefinitionProps<T>): DataFileDefinition<z.infer<T>>;
|
|
115
131
|
|
|
116
|
-
export { type CreateDataFileDefinitionProps, type DataFileContext, type DataFileCore, type DataFileDefinition, type DataFileUpdater, type DataState, type ProviderProps, type UseDataFileCoreProps, createDataFileDefinition, useDataFile, useDataFileContext, useDataFileCore, useDataFileData, useDataFileUpdater };
|
|
132
|
+
export { ArcaneDataFileError, type CreateDataFileDefinitionProps, type DataFileContext, type DataFileCore, type DataFileDefinition, type DataFileOperation, type DataFileUpdater, type DataState, type ErrorListener, type ProviderProps, type UseDataFileCoreProps, createDataFileDefinition, useDataFile, useDataFileContext, useDataFileCore, useDataFileData, useDataFileUpdater };
|
package/dist/data.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ type DataFileUsage = WithPathChange & {
|
|
|
17
17
|
* this will be relative to the current working directory.
|
|
18
18
|
*/
|
|
19
19
|
path: string;
|
|
20
|
+
/** Callback that will be called if any error occurs during loading or saving the file */
|
|
21
|
+
onError?: (error: ArcaneDataFileError) => void;
|
|
20
22
|
};
|
|
21
23
|
type ProviderProps = DataFileUsage & {
|
|
22
24
|
children: ReactNode;
|
|
@@ -43,6 +45,10 @@ type DataFileContext<T> = {
|
|
|
43
45
|
*/
|
|
44
46
|
lastUpdatedMillis: number;
|
|
45
47
|
updateData: DataFileUpdater<T>;
|
|
48
|
+
/**
|
|
49
|
+
* Can be called to reset the data to the default value, and save that to disk.
|
|
50
|
+
*/
|
|
51
|
+
resetData: () => void;
|
|
46
52
|
/**
|
|
47
53
|
* Can be called to force an attempt to re-save the data to disk
|
|
48
54
|
*/
|
|
@@ -93,24 +99,34 @@ type DataState<T> = {
|
|
|
93
99
|
status: 'ready';
|
|
94
100
|
data: T;
|
|
95
101
|
});
|
|
102
|
+
type DataFileOperation = 'load' | 'save' | 'usage';
|
|
103
|
+
declare class ArcaneDataFileError extends Error {
|
|
104
|
+
readonly operation: DataFileOperation;
|
|
105
|
+
readonly path: string | null;
|
|
106
|
+
readonly contents: string | null;
|
|
107
|
+
constructor(message: string, operation: DataFileOperation, path: string | null, contents: string | null, cause?: unknown);
|
|
108
|
+
}
|
|
109
|
+
type ErrorListener = (error: ArcaneDataFileError) => void;
|
|
96
110
|
type UseDataFileCoreProps<T> = WithPathChange & {
|
|
97
111
|
schema: ZodType<T>;
|
|
98
112
|
defaultValue: T;
|
|
99
113
|
path: string;
|
|
114
|
+
onError?: (error: ArcaneDataFileError) => void;
|
|
100
115
|
};
|
|
101
116
|
type DataFileCore<T> = {
|
|
102
117
|
data: DataState<T>;
|
|
103
118
|
updateData: DataFileUpdater<T>;
|
|
119
|
+
resetData: () => void;
|
|
104
120
|
saveData: () => void;
|
|
105
121
|
};
|
|
106
122
|
/**
|
|
107
123
|
* Primary hook for & logic for using data files.
|
|
108
124
|
*/
|
|
109
|
-
declare function useDataFileCore<T>({ schema, defaultValue, path, onPathChange, }: UseDataFileCoreProps<T>): DataFileCore<T>;
|
|
125
|
+
declare function useDataFileCore<T>({ schema, defaultValue, path, onPathChange, onError, }: UseDataFileCoreProps<T>): DataFileCore<T>;
|
|
110
126
|
type CreateDataFileDefinitionProps<T extends ZodType> = {
|
|
111
127
|
schema: T;
|
|
112
128
|
defaultValue: z.infer<T>;
|
|
113
129
|
};
|
|
114
130
|
declare function createDataFileDefinition<T extends ZodType>({ schema, defaultValue, }: CreateDataFileDefinitionProps<T>): DataFileDefinition<z.infer<T>>;
|
|
115
131
|
|
|
116
|
-
export { type CreateDataFileDefinitionProps, type DataFileContext, type DataFileCore, type DataFileDefinition, type DataFileUpdater, type DataState, type ProviderProps, type UseDataFileCoreProps, createDataFileDefinition, useDataFile, useDataFileContext, useDataFileCore, useDataFileData, useDataFileUpdater };
|
|
132
|
+
export { ArcaneDataFileError, type CreateDataFileDefinitionProps, type DataFileContext, type DataFileCore, type DataFileDefinition, type DataFileOperation, type DataFileUpdater, type DataState, type ErrorListener, type ProviderProps, type UseDataFileCoreProps, createDataFileDefinition, useDataFile, useDataFileContext, useDataFileCore, useDataFileData, useDataFileUpdater };
|
package/dist/data.js
CHANGED
|
@@ -4,6 +4,7 @@ var _chunkRT2VSMJLjs = require('./chunk-RT2VSMJL.js');
|
|
|
4
4
|
|
|
5
5
|
// src/data.tsx
|
|
6
6
|
var _fs = require('fs');
|
|
7
|
+
var _crypto = require('crypto');
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
|
|
@@ -27,15 +28,39 @@ function useDataFileContext(dataFile) {
|
|
|
27
28
|
function useDataFile(dataFile, usage) {
|
|
28
29
|
return dataFile.useDataFile(usage);
|
|
29
30
|
}
|
|
31
|
+
var ArcaneDataFileError = class extends Error {
|
|
32
|
+
constructor(message, operation, path, contents, cause) {
|
|
33
|
+
super(message, { cause });
|
|
34
|
+
this.operation = operation;
|
|
35
|
+
this.path = path;
|
|
36
|
+
this.contents = contents;
|
|
37
|
+
this.name = `ArcaneDataFileError(${operation})`;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
function stripUtf8Bom(data) {
|
|
41
|
+
return data.charCodeAt(0) === 65279 ? data.slice(1) : data;
|
|
42
|
+
}
|
|
43
|
+
async function writeFileAtomically(path, data) {
|
|
44
|
+
const tempPath = `${path}.${process.pid}.${_crypto.randomUUID.call(void 0, )}.tmp`;
|
|
45
|
+
try {
|
|
46
|
+
await _fs.promises.writeFile(tempPath, data, "utf8");
|
|
47
|
+
await _fs.promises.rename(tempPath, path);
|
|
48
|
+
} catch (error) {
|
|
49
|
+
await _fs.promises.rm(tempPath, { force: true }).catch(() => void 0);
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
30
53
|
function useDataFileCore({
|
|
31
54
|
schema,
|
|
32
55
|
defaultValue,
|
|
33
56
|
path,
|
|
34
|
-
onPathChange = "defaultValue"
|
|
57
|
+
onPathChange = "defaultValue",
|
|
58
|
+
onError
|
|
35
59
|
}) {
|
|
36
60
|
const log = _chunkRT2VSMJLjs.useLogger.call(void 0, );
|
|
37
61
|
const state = _react.useRef.call(void 0, {
|
|
38
62
|
initialized: false,
|
|
63
|
+
saveChain: Promise.resolve(),
|
|
39
64
|
path: null,
|
|
40
65
|
data: void 0,
|
|
41
66
|
previousData: void 0,
|
|
@@ -48,9 +73,14 @@ function useDataFileCore({
|
|
|
48
73
|
if (!state.current.initialized) {
|
|
49
74
|
state.current.initialized = true;
|
|
50
75
|
} else {
|
|
51
|
-
|
|
52
|
-
"Cannot change schema or defaultValue after initialization"
|
|
76
|
+
const error = new ArcaneDataFileError(
|
|
77
|
+
"Cannot change schema or defaultValue after initialization",
|
|
78
|
+
"usage",
|
|
79
|
+
null,
|
|
80
|
+
null
|
|
53
81
|
);
|
|
82
|
+
_optionalChain([onError, 'optionalCall', _ => _(error)]);
|
|
83
|
+
throw error;
|
|
54
84
|
}
|
|
55
85
|
}, [schema, defaultValue]);
|
|
56
86
|
const [data, setData] = _react.useState.call(void 0, {
|
|
@@ -97,15 +127,27 @@ function useDataFileCore({
|
|
|
97
127
|
if (!currentPath || currentData === void 0) {
|
|
98
128
|
return;
|
|
99
129
|
}
|
|
100
|
-
|
|
101
|
-
|
|
130
|
+
const json = JSON.stringify(currentData, null, 2);
|
|
131
|
+
const queuedSave = state.current.saveChain.catch(() => void 0).then(async () => {
|
|
102
132
|
await _fs.promises.mkdir(_path.dirname.call(void 0, currentPath), { recursive: true });
|
|
103
|
-
await
|
|
104
|
-
|
|
133
|
+
await writeFileAtomically(currentPath, json);
|
|
134
|
+
});
|
|
135
|
+
try {
|
|
136
|
+
state.current.saveChain = queuedSave;
|
|
137
|
+
await queuedSave;
|
|
138
|
+
if (state.current.path === currentPath && state.current.saveChain === queuedSave) {
|
|
105
139
|
state.current.state = { state: "saved" };
|
|
106
140
|
}
|
|
107
|
-
} catch (
|
|
108
|
-
|
|
141
|
+
} catch (cause) {
|
|
142
|
+
const error = new ArcaneDataFileError(
|
|
143
|
+
`Error saving data file to path: ${currentPath}`,
|
|
144
|
+
"save",
|
|
145
|
+
currentPath,
|
|
146
|
+
null,
|
|
147
|
+
cause
|
|
148
|
+
);
|
|
149
|
+
_optionalChain([onError, 'optionalCall', _2 => _2(error)]);
|
|
150
|
+
if (state.current.path === currentPath && state.current.saveChain === queuedSave) {
|
|
109
151
|
state.current.state = { state: "error", error };
|
|
110
152
|
updateDataFromState();
|
|
111
153
|
}
|
|
@@ -132,24 +174,26 @@ function useDataFileCore({
|
|
|
132
174
|
state: "saved"
|
|
133
175
|
}
|
|
134
176
|
};
|
|
177
|
+
let contents = null;
|
|
135
178
|
_fs.promises.readFile(path, "utf8").then((data2) => {
|
|
136
|
-
|
|
179
|
+
contents = data2;
|
|
180
|
+
const parsedData = schema.parse(JSON.parse(stripUtf8Bom(data2)));
|
|
137
181
|
if (state.current.path === path) {
|
|
138
182
|
state.current.data = parsedData;
|
|
139
183
|
state.current.lastUpdatedMillis = Date.now();
|
|
140
184
|
state.current.state = { state: "saved" };
|
|
141
185
|
updateDataFromState();
|
|
142
186
|
}
|
|
143
|
-
}).catch((
|
|
187
|
+
}).catch((err) => {
|
|
144
188
|
if (state.current.path !== path) {
|
|
145
189
|
return;
|
|
146
190
|
}
|
|
147
|
-
if (
|
|
191
|
+
if (err.code === "ENOENT") {
|
|
148
192
|
const initialData = onPathChange === "transfer" && state.current.previousData !== void 0 ? state.current.previousData : defaultValue;
|
|
149
193
|
state.current.data = initialData;
|
|
150
194
|
state.current.lastUpdatedMillis = Date.now();
|
|
151
195
|
state.current.state = { state: "dirty" };
|
|
152
|
-
_optionalChain([log, 'optionalAccess',
|
|
196
|
+
_optionalChain([log, 'optionalAccess', _3 => _3.info, 'call', _4 => _4(
|
|
153
197
|
"Creating a new file at %s with initial data %o",
|
|
154
198
|
path,
|
|
155
199
|
initialData
|
|
@@ -158,29 +202,49 @@ function useDataFileCore({
|
|
|
158
202
|
updateDataFromState();
|
|
159
203
|
return;
|
|
160
204
|
}
|
|
205
|
+
const error = new ArcaneDataFileError(
|
|
206
|
+
`Error loading data file at path: ${path}`,
|
|
207
|
+
"load",
|
|
208
|
+
path,
|
|
209
|
+
contents,
|
|
210
|
+
err
|
|
211
|
+
);
|
|
212
|
+
_optionalChain([onError, 'optionalCall', _5 => _5(error)]);
|
|
213
|
+
_optionalChain([log, 'optionalAccess', _6 => _6.error, 'call', _7 => _7(error)]);
|
|
161
214
|
state.current.state = { state: "error", error };
|
|
162
215
|
updateDataFromState();
|
|
163
216
|
});
|
|
164
217
|
}, [path, onPathChange]);
|
|
165
|
-
const
|
|
166
|
-
() => (
|
|
218
|
+
const setDataTo = _react.useMemo.call(void 0,
|
|
219
|
+
() => (data2) => {
|
|
167
220
|
if (state.current.path !== path) {
|
|
168
221
|
return;
|
|
169
222
|
}
|
|
170
|
-
|
|
171
|
-
throw new Error("Attempt to update data before it has been loaded");
|
|
172
|
-
}
|
|
173
|
-
state.current.data = update(state.current.data);
|
|
223
|
+
state.current.data = data2;
|
|
174
224
|
state.current.lastUpdatedMillis = Date.now();
|
|
175
225
|
state.current.state = { state: "dirty" };
|
|
176
226
|
saveData();
|
|
177
227
|
updateDataFromState();
|
|
178
228
|
},
|
|
179
|
-
[path]
|
|
229
|
+
[saveData, path]
|
|
230
|
+
);
|
|
231
|
+
const updateData = _react.useMemo.call(void 0,
|
|
232
|
+
() => (update) => {
|
|
233
|
+
if (state.current.data === void 0) {
|
|
234
|
+
throw new Error("Attempt to update data before it has been loaded");
|
|
235
|
+
}
|
|
236
|
+
setDataTo(update(state.current.data));
|
|
237
|
+
},
|
|
238
|
+
[setDataTo]
|
|
239
|
+
);
|
|
240
|
+
const resetData = _react.useMemo.call(void 0,
|
|
241
|
+
() => () => setDataTo(defaultValue),
|
|
242
|
+
[setDataTo, defaultValue]
|
|
180
243
|
);
|
|
181
244
|
return {
|
|
182
245
|
data,
|
|
183
246
|
updateData,
|
|
247
|
+
resetData,
|
|
184
248
|
saveData
|
|
185
249
|
};
|
|
186
250
|
}
|
|
@@ -195,6 +259,9 @@ function createDataFileDefinition({
|
|
|
195
259
|
updateData: () => {
|
|
196
260
|
throw new Error("Data file provider not used");
|
|
197
261
|
},
|
|
262
|
+
resetData: () => {
|
|
263
|
+
throw new Error("Data file provider not used");
|
|
264
|
+
},
|
|
198
265
|
saveData: () => {
|
|
199
266
|
throw new Error("Data file provider not used");
|
|
200
267
|
},
|
|
@@ -202,17 +269,25 @@ function createDataFileDefinition({
|
|
|
202
269
|
});
|
|
203
270
|
const useDataFile2 = ({
|
|
204
271
|
path,
|
|
205
|
-
onPathChange
|
|
272
|
+
onPathChange,
|
|
273
|
+
onError
|
|
206
274
|
}) => useDataFileCore({
|
|
207
275
|
schema,
|
|
208
276
|
defaultValue,
|
|
209
277
|
path,
|
|
210
|
-
onPathChange
|
|
278
|
+
onPathChange,
|
|
279
|
+
onError
|
|
211
280
|
});
|
|
212
|
-
const Provider = ({
|
|
213
|
-
|
|
281
|
+
const Provider = ({
|
|
282
|
+
path,
|
|
283
|
+
onPathChange,
|
|
284
|
+
onError,
|
|
285
|
+
children
|
|
286
|
+
}) => {
|
|
287
|
+
const { data, updateData, resetData, saveData } = useDataFile2({
|
|
214
288
|
path,
|
|
215
|
-
onPathChange
|
|
289
|
+
onPathChange,
|
|
290
|
+
onError
|
|
216
291
|
});
|
|
217
292
|
const providedContext = _react.useMemo.call(void 0,
|
|
218
293
|
() => ({
|
|
@@ -220,6 +295,7 @@ function createDataFileDefinition({
|
|
|
220
295
|
status: data.status,
|
|
221
296
|
lastUpdatedMillis: data.lastUpdatedMillis,
|
|
222
297
|
updateData,
|
|
298
|
+
resetData,
|
|
223
299
|
saveData,
|
|
224
300
|
error: data.status === "error" ? data.error : void 0
|
|
225
301
|
}),
|
|
@@ -243,4 +319,5 @@ function createDataFileDefinition({
|
|
|
243
319
|
|
|
244
320
|
|
|
245
321
|
|
|
246
|
-
|
|
322
|
+
|
|
323
|
+
exports.ArcaneDataFileError = ArcaneDataFileError; exports.createDataFileDefinition = createDataFileDefinition; exports.useDataFile = useDataFile; exports.useDataFileContext = useDataFileContext; exports.useDataFileCore = useDataFileCore; exports.useDataFileData = useDataFileData; exports.useDataFileUpdater = useDataFileUpdater;
|
package/dist/data.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
|
|
5
5
|
// src/data.tsx
|
|
6
6
|
import { promises as fs } from "fs";
|
|
7
|
+
import { randomUUID } from "crypto";
|
|
7
8
|
import {
|
|
8
9
|
useMemo,
|
|
9
10
|
useContext,
|
|
@@ -27,15 +28,39 @@ function useDataFileContext(dataFile) {
|
|
|
27
28
|
function useDataFile(dataFile, usage) {
|
|
28
29
|
return dataFile.useDataFile(usage);
|
|
29
30
|
}
|
|
31
|
+
var ArcaneDataFileError = class extends Error {
|
|
32
|
+
constructor(message, operation, path, contents, cause) {
|
|
33
|
+
super(message, { cause });
|
|
34
|
+
this.operation = operation;
|
|
35
|
+
this.path = path;
|
|
36
|
+
this.contents = contents;
|
|
37
|
+
this.name = `ArcaneDataFileError(${operation})`;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
function stripUtf8Bom(data) {
|
|
41
|
+
return data.charCodeAt(0) === 65279 ? data.slice(1) : data;
|
|
42
|
+
}
|
|
43
|
+
async function writeFileAtomically(path, data) {
|
|
44
|
+
const tempPath = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
45
|
+
try {
|
|
46
|
+
await fs.writeFile(tempPath, data, "utf8");
|
|
47
|
+
await fs.rename(tempPath, path);
|
|
48
|
+
} catch (error) {
|
|
49
|
+
await fs.rm(tempPath, { force: true }).catch(() => void 0);
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
30
53
|
function useDataFileCore({
|
|
31
54
|
schema,
|
|
32
55
|
defaultValue,
|
|
33
56
|
path,
|
|
34
|
-
onPathChange = "defaultValue"
|
|
57
|
+
onPathChange = "defaultValue",
|
|
58
|
+
onError
|
|
35
59
|
}) {
|
|
36
60
|
const log = useLogger();
|
|
37
61
|
const state = useRef({
|
|
38
62
|
initialized: false,
|
|
63
|
+
saveChain: Promise.resolve(),
|
|
39
64
|
path: null,
|
|
40
65
|
data: void 0,
|
|
41
66
|
previousData: void 0,
|
|
@@ -48,9 +73,14 @@ function useDataFileCore({
|
|
|
48
73
|
if (!state.current.initialized) {
|
|
49
74
|
state.current.initialized = true;
|
|
50
75
|
} else {
|
|
51
|
-
|
|
52
|
-
"Cannot change schema or defaultValue after initialization"
|
|
76
|
+
const error = new ArcaneDataFileError(
|
|
77
|
+
"Cannot change schema or defaultValue after initialization",
|
|
78
|
+
"usage",
|
|
79
|
+
null,
|
|
80
|
+
null
|
|
53
81
|
);
|
|
82
|
+
onError?.(error);
|
|
83
|
+
throw error;
|
|
54
84
|
}
|
|
55
85
|
}, [schema, defaultValue]);
|
|
56
86
|
const [data, setData] = useState({
|
|
@@ -97,15 +127,27 @@ function useDataFileCore({
|
|
|
97
127
|
if (!currentPath || currentData === void 0) {
|
|
98
128
|
return;
|
|
99
129
|
}
|
|
100
|
-
|
|
101
|
-
|
|
130
|
+
const json = JSON.stringify(currentData, null, 2);
|
|
131
|
+
const queuedSave = state.current.saveChain.catch(() => void 0).then(async () => {
|
|
102
132
|
await fs.mkdir(dirname(currentPath), { recursive: true });
|
|
103
|
-
await
|
|
104
|
-
|
|
133
|
+
await writeFileAtomically(currentPath, json);
|
|
134
|
+
});
|
|
135
|
+
try {
|
|
136
|
+
state.current.saveChain = queuedSave;
|
|
137
|
+
await queuedSave;
|
|
138
|
+
if (state.current.path === currentPath && state.current.saveChain === queuedSave) {
|
|
105
139
|
state.current.state = { state: "saved" };
|
|
106
140
|
}
|
|
107
|
-
} catch (
|
|
108
|
-
|
|
141
|
+
} catch (cause) {
|
|
142
|
+
const error = new ArcaneDataFileError(
|
|
143
|
+
`Error saving data file to path: ${currentPath}`,
|
|
144
|
+
"save",
|
|
145
|
+
currentPath,
|
|
146
|
+
null,
|
|
147
|
+
cause
|
|
148
|
+
);
|
|
149
|
+
onError?.(error);
|
|
150
|
+
if (state.current.path === currentPath && state.current.saveChain === queuedSave) {
|
|
109
151
|
state.current.state = { state: "error", error };
|
|
110
152
|
updateDataFromState();
|
|
111
153
|
}
|
|
@@ -132,19 +174,21 @@ function useDataFileCore({
|
|
|
132
174
|
state: "saved"
|
|
133
175
|
}
|
|
134
176
|
};
|
|
177
|
+
let contents = null;
|
|
135
178
|
fs.readFile(path, "utf8").then((data2) => {
|
|
136
|
-
|
|
179
|
+
contents = data2;
|
|
180
|
+
const parsedData = schema.parse(JSON.parse(stripUtf8Bom(data2)));
|
|
137
181
|
if (state.current.path === path) {
|
|
138
182
|
state.current.data = parsedData;
|
|
139
183
|
state.current.lastUpdatedMillis = Date.now();
|
|
140
184
|
state.current.state = { state: "saved" };
|
|
141
185
|
updateDataFromState();
|
|
142
186
|
}
|
|
143
|
-
}).catch((
|
|
187
|
+
}).catch((err) => {
|
|
144
188
|
if (state.current.path !== path) {
|
|
145
189
|
return;
|
|
146
190
|
}
|
|
147
|
-
if (
|
|
191
|
+
if (err.code === "ENOENT") {
|
|
148
192
|
const initialData = onPathChange === "transfer" && state.current.previousData !== void 0 ? state.current.previousData : defaultValue;
|
|
149
193
|
state.current.data = initialData;
|
|
150
194
|
state.current.lastUpdatedMillis = Date.now();
|
|
@@ -158,29 +202,49 @@ function useDataFileCore({
|
|
|
158
202
|
updateDataFromState();
|
|
159
203
|
return;
|
|
160
204
|
}
|
|
205
|
+
const error = new ArcaneDataFileError(
|
|
206
|
+
`Error loading data file at path: ${path}`,
|
|
207
|
+
"load",
|
|
208
|
+
path,
|
|
209
|
+
contents,
|
|
210
|
+
err
|
|
211
|
+
);
|
|
212
|
+
onError?.(error);
|
|
213
|
+
log?.error(error);
|
|
161
214
|
state.current.state = { state: "error", error };
|
|
162
215
|
updateDataFromState();
|
|
163
216
|
});
|
|
164
217
|
}, [path, onPathChange]);
|
|
165
|
-
const
|
|
166
|
-
() => (
|
|
218
|
+
const setDataTo = useMemo(
|
|
219
|
+
() => (data2) => {
|
|
167
220
|
if (state.current.path !== path) {
|
|
168
221
|
return;
|
|
169
222
|
}
|
|
170
|
-
|
|
171
|
-
throw new Error("Attempt to update data before it has been loaded");
|
|
172
|
-
}
|
|
173
|
-
state.current.data = update(state.current.data);
|
|
223
|
+
state.current.data = data2;
|
|
174
224
|
state.current.lastUpdatedMillis = Date.now();
|
|
175
225
|
state.current.state = { state: "dirty" };
|
|
176
226
|
saveData();
|
|
177
227
|
updateDataFromState();
|
|
178
228
|
},
|
|
179
|
-
[path]
|
|
229
|
+
[saveData, path]
|
|
230
|
+
);
|
|
231
|
+
const updateData = useMemo(
|
|
232
|
+
() => (update) => {
|
|
233
|
+
if (state.current.data === void 0) {
|
|
234
|
+
throw new Error("Attempt to update data before it has been loaded");
|
|
235
|
+
}
|
|
236
|
+
setDataTo(update(state.current.data));
|
|
237
|
+
},
|
|
238
|
+
[setDataTo]
|
|
239
|
+
);
|
|
240
|
+
const resetData = useMemo(
|
|
241
|
+
() => () => setDataTo(defaultValue),
|
|
242
|
+
[setDataTo, defaultValue]
|
|
180
243
|
);
|
|
181
244
|
return {
|
|
182
245
|
data,
|
|
183
246
|
updateData,
|
|
247
|
+
resetData,
|
|
184
248
|
saveData
|
|
185
249
|
};
|
|
186
250
|
}
|
|
@@ -195,6 +259,9 @@ function createDataFileDefinition({
|
|
|
195
259
|
updateData: () => {
|
|
196
260
|
throw new Error("Data file provider not used");
|
|
197
261
|
},
|
|
262
|
+
resetData: () => {
|
|
263
|
+
throw new Error("Data file provider not used");
|
|
264
|
+
},
|
|
198
265
|
saveData: () => {
|
|
199
266
|
throw new Error("Data file provider not used");
|
|
200
267
|
},
|
|
@@ -202,17 +269,25 @@ function createDataFileDefinition({
|
|
|
202
269
|
});
|
|
203
270
|
const useDataFile2 = ({
|
|
204
271
|
path,
|
|
205
|
-
onPathChange
|
|
272
|
+
onPathChange,
|
|
273
|
+
onError
|
|
206
274
|
}) => useDataFileCore({
|
|
207
275
|
schema,
|
|
208
276
|
defaultValue,
|
|
209
277
|
path,
|
|
210
|
-
onPathChange
|
|
278
|
+
onPathChange,
|
|
279
|
+
onError
|
|
211
280
|
});
|
|
212
|
-
const Provider = ({
|
|
213
|
-
|
|
281
|
+
const Provider = ({
|
|
282
|
+
path,
|
|
283
|
+
onPathChange,
|
|
284
|
+
onError,
|
|
285
|
+
children
|
|
286
|
+
}) => {
|
|
287
|
+
const { data, updateData, resetData, saveData } = useDataFile2({
|
|
214
288
|
path,
|
|
215
|
-
onPathChange
|
|
289
|
+
onPathChange,
|
|
290
|
+
onError
|
|
216
291
|
});
|
|
217
292
|
const providedContext = useMemo(
|
|
218
293
|
() => ({
|
|
@@ -220,6 +295,7 @@ function createDataFileDefinition({
|
|
|
220
295
|
status: data.status,
|
|
221
296
|
lastUpdatedMillis: data.lastUpdatedMillis,
|
|
222
297
|
updateData,
|
|
298
|
+
resetData,
|
|
223
299
|
saveData,
|
|
224
300
|
error: data.status === "error" ? data.error : void 0
|
|
225
301
|
}),
|
|
@@ -237,6 +313,7 @@ function createDataFileDefinition({
|
|
|
237
313
|
};
|
|
238
314
|
}
|
|
239
315
|
export {
|
|
316
|
+
ArcaneDataFileError,
|
|
240
317
|
createDataFileDefinition,
|
|
241
318
|
useDataFile,
|
|
242
319
|
useDataFileContext,
|