@arcanejs/react-toolkit 0.2.0 → 0.3.1

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 CHANGED
@@ -1,8 +1,24 @@
1
1
  import { ReactNode, FC, Context } from 'react';
2
2
  import { ZodType } from 'zod';
3
3
 
4
- type ProviderProps = {
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
+ */
5
19
  path: string;
20
+ };
21
+ type ProviderProps = DataFileUsage & {
6
22
  children: ReactNode;
7
23
  };
8
24
  type DataFileUpdater<T> = (update: (current: T) => T) => void;
@@ -21,25 +37,60 @@ type DataFileContext<T> = {
21
37
  */
22
38
  error: unknown;
23
39
  };
24
- type DataFile<T> = {
40
+ type DataFileDefinition<T> = {
25
41
  Provider: FC<ProviderProps>;
26
42
  context: Context<DataFileContext<T>>;
43
+ useDataFile: (props: DataFileUsage) => DataFileCore<T>;
27
44
  };
28
- type DataFileProps<T> = {
29
- schema: ZodType<T>;
30
- defaultValue: T;
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';
31
58
  /**
32
- * When the file path changes and the file does not yet exist,
33
- * should the previous data be stored in the new file
34
- * or should the new file be reset to the default value?
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`.
35
62
  *
36
- * @default 'defaultValue'
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.
37
66
  */
38
- onPathChange?: 'transfer' | 'defaultValue';
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;
39
93
  };
40
- declare function useDataFileData<T>(dataFile: DataFile<T>): T;
41
- declare function useDataFileUpdater<T>(dataFile: DataFile<T>): DataFileUpdater<T>;
42
- declare function useDataFile<T>(dataFile: DataFile<T>): DataFileContext<T>;
43
- declare function createDataFileSpec<T>({ schema, defaultValue, onPathChange, }: DataFileProps<T>): DataFile<T>;
94
+ declare function createDataFileDefinition<T>({ schema, defaultValue, }: CreateDataFileDefinitionProps<T>): DataFileDefinition<T>;
44
95
 
45
- export { type ProviderProps, createDataFileSpec, useDataFile, useDataFileData, useDataFileUpdater };
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 CHANGED
@@ -1,8 +1,24 @@
1
1
  import { ReactNode, FC, Context } from 'react';
2
2
  import { ZodType } from 'zod';
3
3
 
4
- type ProviderProps = {
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
+ */
5
19
  path: string;
20
+ };
21
+ type ProviderProps = DataFileUsage & {
6
22
  children: ReactNode;
7
23
  };
8
24
  type DataFileUpdater<T> = (update: (current: T) => T) => void;
@@ -21,25 +37,60 @@ type DataFileContext<T> = {
21
37
  */
22
38
  error: unknown;
23
39
  };
24
- type DataFile<T> = {
40
+ type DataFileDefinition<T> = {
25
41
  Provider: FC<ProviderProps>;
26
42
  context: Context<DataFileContext<T>>;
43
+ useDataFile: (props: DataFileUsage) => DataFileCore<T>;
27
44
  };
28
- type DataFileProps<T> = {
29
- schema: ZodType<T>;
30
- defaultValue: T;
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';
31
58
  /**
32
- * When the file path changes and the file does not yet exist,
33
- * should the previous data be stored in the new file
34
- * or should the new file be reset to the default value?
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`.
35
62
  *
36
- * @default 'defaultValue'
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.
37
66
  */
38
- onPathChange?: 'transfer' | 'defaultValue';
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;
39
93
  };
40
- declare function useDataFileData<T>(dataFile: DataFile<T>): T;
41
- declare function useDataFileUpdater<T>(dataFile: DataFile<T>): DataFileUpdater<T>;
42
- declare function useDataFile<T>(dataFile: DataFile<T>): DataFileContext<T>;
43
- declare function createDataFileSpec<T>({ schema, defaultValue, onPathChange, }: DataFileProps<T>): DataFile<T>;
94
+ declare function createDataFileDefinition<T>({ schema, defaultValue, }: CreateDataFileDefinitionProps<T>): DataFileDefinition<T>;
44
95
 
45
- export { type ProviderProps, createDataFileSpec, useDataFile, useDataFileData, useDataFileUpdater };
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 CHANGED
@@ -20,8 +20,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/data.tsx
21
21
  var data_exports = {};
22
22
  __export(data_exports, {
23
- createDataFileSpec: () => createDataFileSpec,
23
+ createDataFileDefinition: () => createDataFileDefinition,
24
24
  useDataFile: () => useDataFile,
25
+ useDataFileContext: () => useDataFileContext,
26
+ useDataFileCore: () => useDataFileCore,
25
27
  useDataFileData: () => useDataFileData,
26
28
  useDataFileUpdater: () => useDataFileUpdater
27
29
  });
@@ -37,149 +39,189 @@ function useDataFileData(dataFile) {
37
39
  function useDataFileUpdater(dataFile) {
38
40
  return (0, import_react.useContext)(dataFile.context).updateData;
39
41
  }
40
- function useDataFile(dataFile) {
42
+ function useDataFileContext(dataFile) {
41
43
  return (0, import_react.useContext)(dataFile.context);
42
44
  }
43
- function createDataFileSpec({
45
+ function useDataFile(dataFile, usage) {
46
+ return dataFile.useDataFile(usage);
47
+ }
48
+ function useDataFileCore({
44
49
  schema,
45
50
  defaultValue,
51
+ path,
46
52
  onPathChange = "defaultValue"
47
53
  }) {
48
- const context = (0, import_react.createContext)({
49
- data: defaultValue,
50
- updateData: () => {
51
- throw new Error("Data file provider not used");
52
- },
53
- saveData: () => {
54
- throw new Error("Data file provider not used");
55
- },
56
- error: void 0
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
+ }
57
62
  });
58
- const Provider = ({ path, children }) => {
59
- const state = (0, import_react.useRef)({
60
- path: null,
61
- data: void 0,
62
- previousData: void 0,
63
- state: {
64
- state: "saved"
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;
65
86
  }
66
- });
67
- const [data, setData] = (0, import_react.useState)({
68
- status: "loading"
69
- });
70
- const updateDataFromState = (0, import_react.useMemo)(
71
- () => () => {
72
- const data2 = state.current.data;
73
- if (state.current.state.state === "error") {
74
- setData({
75
- status: "error",
76
- error: state.current.state.error,
77
- data: data2
78
- });
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") {
79
105
  return;
80
106
  }
81
- if (data2 === void 0) {
82
- setData({
83
- status: "loading"
84
- });
107
+ const currentPath = state.current.path;
108
+ const currentData = state.current.data;
109
+ if (!currentPath || currentData === void 0) {
85
110
  return;
86
111
  }
87
- setData({
88
- status: "ready",
89
- data: data2
90
- });
91
- },
92
- []
93
- );
94
- const requestFileWriteToDisk = (0, import_react.useMemo)(
95
- () => (0, import_lodash.throttle)(
96
- async () => {
97
- if (state.current.state.state === "saved") {
98
- return;
99
- }
100
- const currentPath = state.current.path;
101
- const currentData = state.current.data;
102
- if (!currentPath || currentData === void 0) {
103
- return;
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" };
104
118
  }
105
- try {
106
- const json = JSON.stringify(currentData, null, 2);
107
- await import_fs.promises.mkdir((0, import_path.dirname)(currentPath), { recursive: true });
108
- await import_fs.promises.writeFile(currentPath, json, "utf8");
109
- if (state.current.path === currentPath && state.current.data === currentData) {
110
- state.current.state = { state: "saved" };
111
- }
112
- } catch (error) {
113
- if (state.current.path === currentPath && state.current.data === currentData) {
114
- state.current.state = { state: "error", error };
115
- updateDataFromState();
116
- }
119
+ } catch (error) {
120
+ if (state.current.path === currentPath && state.current.data === currentData) {
121
+ state.current.state = { state: "error", error };
122
+ updateDataFromState();
117
123
  }
118
- },
119
- 500,
120
- {
121
- // Write leading so that we always write to disk quickly when
122
- // only single things have changed
123
- leading: true,
124
- // Trailing is important otherwise we may lose data
125
- trailing: true
126
- }
127
- ),
128
- []
129
- );
130
- (0, import_react.useEffect)(() => {
131
- state.current = {
132
- path,
133
- data: void 0,
134
- previousData: state.current.data ?? state.current.previousData,
135
- state: {
136
- state: "saved"
137
124
  }
138
- };
139
- import_fs.promises.readFile(path, "utf8").then((data2) => {
140
- const parsedData = schema.parse(JSON.parse(data2));
141
- if (state.current.path === path) {
142
- state.current.data = parsedData;
143
- state.current.state = { state: "saved" };
144
- updateDataFromState();
145
- }
146
- }).catch((error) => {
147
- if (state.current.path !== path) {
148
- return;
149
- }
150
- if (error.code === "ENOENT") {
151
- console.log("Creating new file");
152
- const initialData = onPathChange === "transfer" && state.current.previousData !== void 0 ? state.current.previousData : defaultValue;
153
- state.current.data = initialData;
154
- state.current.state = { state: "dirty" };
155
- requestFileWriteToDisk();
156
- updateDataFromState();
157
- return;
158
- }
159
- state.current.state = { state: "error", error };
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" };
160
152
  updateDataFromState();
161
- });
162
- }, [path]);
163
- const updateData = (0, import_react.useMemo)(
164
- () => (update) => {
165
- if (state.current.path !== path) {
166
- return;
167
- }
168
- if (state.current.data === void 0) {
169
- throw new Error("Attempt to update data before it has been loaded");
170
- }
171
- state.current.data = update(state.current.data);
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;
172
162
  state.current.state = { state: "dirty" };
173
- requestFileWriteToDisk();
163
+ saveData();
174
164
  updateDataFromState();
175
- },
176
- [path]
177
- );
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
+ });
178
220
  const providedContext = (0, import_react.useMemo)(
179
221
  () => ({
180
222
  data: data.status !== "loading" && data.data !== void 0 ? data.data : defaultValue,
181
223
  updateData,
182
- saveData: requestFileWriteToDisk,
224
+ saveData,
183
225
  error: data.status === "error" ? data.error : void 0
184
226
  }),
185
227
  [data, updateData]
@@ -191,13 +233,16 @@ function createDataFileSpec({
191
233
  };
192
234
  return {
193
235
  Provider,
194
- context
236
+ context,
237
+ useDataFile: useDataFile2
195
238
  };
196
239
  }
197
240
  // Annotate the CommonJS export names for ESM import in node:
198
241
  0 && (module.exports = {
199
- createDataFileSpec,
242
+ createDataFileDefinition,
200
243
  useDataFile,
244
+ useDataFileContext,
245
+ useDataFileCore,
201
246
  useDataFileData,
202
247
  useDataFileUpdater
203
248
  });
package/dist/data.mjs CHANGED
@@ -17,149 +17,189 @@ function useDataFileData(dataFile) {
17
17
  function useDataFileUpdater(dataFile) {
18
18
  return useContext(dataFile.context).updateData;
19
19
  }
20
- function useDataFile(dataFile) {
20
+ function useDataFileContext(dataFile) {
21
21
  return useContext(dataFile.context);
22
22
  }
23
- function createDataFileSpec({
23
+ function useDataFile(dataFile, usage) {
24
+ return dataFile.useDataFile(usage);
25
+ }
26
+ function useDataFileCore({
24
27
  schema,
25
28
  defaultValue,
29
+ path,
26
30
  onPathChange = "defaultValue"
27
31
  }) {
28
- const context = createContext({
29
- data: defaultValue,
30
- updateData: () => {
31
- throw new Error("Data file provider not used");
32
- },
33
- saveData: () => {
34
- throw new Error("Data file provider not used");
35
- },
36
- error: void 0
32
+ const state = useRef({
33
+ initialized: false,
34
+ path: null,
35
+ data: void 0,
36
+ previousData: void 0,
37
+ state: {
38
+ state: "saved"
39
+ }
37
40
  });
38
- const Provider = ({ path, children }) => {
39
- const state = useRef({
40
- path: null,
41
- data: void 0,
42
- previousData: void 0,
43
- state: {
44
- state: "saved"
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;
45
64
  }
46
- });
47
- const [data, setData] = useState({
48
- status: "loading"
49
- });
50
- const updateDataFromState = useMemo(
51
- () => () => {
52
- const data2 = state.current.data;
53
- if (state.current.state.state === "error") {
54
- setData({
55
- status: "error",
56
- error: state.current.state.error,
57
- data: data2
58
- });
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") {
59
83
  return;
60
84
  }
61
- if (data2 === void 0) {
62
- setData({
63
- status: "loading"
64
- });
85
+ const currentPath = state.current.path;
86
+ const currentData = state.current.data;
87
+ if (!currentPath || currentData === void 0) {
65
88
  return;
66
89
  }
67
- setData({
68
- status: "ready",
69
- data: data2
70
- });
71
- },
72
- []
73
- );
74
- const requestFileWriteToDisk = useMemo(
75
- () => throttle(
76
- async () => {
77
- if (state.current.state.state === "saved") {
78
- return;
79
- }
80
- const currentPath = state.current.path;
81
- const currentData = state.current.data;
82
- if (!currentPath || currentData === void 0) {
83
- return;
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" };
84
96
  }
85
- try {
86
- const json = JSON.stringify(currentData, null, 2);
87
- await fs.mkdir(dirname(currentPath), { recursive: true });
88
- await fs.writeFile(currentPath, json, "utf8");
89
- if (state.current.path === currentPath && state.current.data === currentData) {
90
- state.current.state = { state: "saved" };
91
- }
92
- } catch (error) {
93
- if (state.current.path === currentPath && state.current.data === currentData) {
94
- state.current.state = { state: "error", error };
95
- updateDataFromState();
96
- }
97
+ } catch (error) {
98
+ if (state.current.path === currentPath && state.current.data === currentData) {
99
+ state.current.state = { state: "error", error };
100
+ updateDataFromState();
97
101
  }
98
- },
99
- 500,
100
- {
101
- // Write leading so that we always write to disk quickly when
102
- // only single things have changed
103
- leading: true,
104
- // Trailing is important otherwise we may lose data
105
- trailing: true
106
- }
107
- ),
108
- []
109
- );
110
- useEffect(() => {
111
- state.current = {
112
- path,
113
- data: void 0,
114
- previousData: state.current.data ?? state.current.previousData,
115
- state: {
116
- state: "saved"
117
102
  }
118
- };
119
- fs.readFile(path, "utf8").then((data2) => {
120
- const parsedData = schema.parse(JSON.parse(data2));
121
- if (state.current.path === path) {
122
- state.current.data = parsedData;
123
- state.current.state = { state: "saved" };
124
- updateDataFromState();
125
- }
126
- }).catch((error) => {
127
- if (state.current.path !== path) {
128
- return;
129
- }
130
- if (error.code === "ENOENT") {
131
- console.log("Creating new file");
132
- const initialData = onPathChange === "transfer" && state.current.previousData !== void 0 ? state.current.previousData : defaultValue;
133
- state.current.data = initialData;
134
- state.current.state = { state: "dirty" };
135
- requestFileWriteToDisk();
136
- updateDataFromState();
137
- return;
138
- }
139
- state.current.state = { state: "error", error };
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" };
140
130
  updateDataFromState();
141
- });
142
- }, [path]);
143
- const updateData = useMemo(
144
- () => (update) => {
145
- if (state.current.path !== path) {
146
- return;
147
- }
148
- if (state.current.data === void 0) {
149
- throw new Error("Attempt to update data before it has been loaded");
150
- }
151
- state.current.data = update(state.current.data);
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;
152
140
  state.current.state = { state: "dirty" };
153
- requestFileWriteToDisk();
141
+ saveData();
154
142
  updateDataFromState();
155
- },
156
- [path]
157
- );
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
+ });
158
198
  const providedContext = useMemo(
159
199
  () => ({
160
200
  data: data.status !== "loading" && data.data !== void 0 ? data.data : defaultValue,
161
201
  updateData,
162
- saveData: requestFileWriteToDisk,
202
+ saveData,
163
203
  error: data.status === "error" ? data.error : void 0
164
204
  }),
165
205
  [data, updateData]
@@ -171,12 +211,15 @@ function createDataFileSpec({
171
211
  };
172
212
  return {
173
213
  Provider,
174
- context
214
+ context,
215
+ useDataFile: useDataFile2
175
216
  };
176
217
  }
177
218
  export {
178
- createDataFileSpec,
219
+ createDataFileDefinition,
179
220
  useDataFile,
221
+ useDataFileContext,
222
+ useDataFileCore,
180
223
  useDataFileData,
181
224
  useDataFileUpdater
182
225
  };
package/dist/index.d.mts CHANGED
@@ -11,7 +11,7 @@ import { Props as Props$7, Timeline as Timeline$1 } from '@arcanejs/toolkit/comp
11
11
  import * as React from 'react';
12
12
  import { Ref } from 'react';
13
13
 
14
- type Child = JSX.Element | string | null | undefined;
14
+ type Child = JSX.Element | string | null | undefined | boolean;
15
15
  type Children = Child | Child[];
16
16
  interface LightDeskIntrinsicElements {
17
17
  button: Props & {
package/dist/index.d.ts CHANGED
@@ -11,7 +11,7 @@ import { Props as Props$7, Timeline as Timeline$1 } from '@arcanejs/toolkit/comp
11
11
  import * as React from 'react';
12
12
  import { Ref } from 'react';
13
13
 
14
- type Child = JSX.Element | string | null | undefined;
14
+ type Child = JSX.Element | string | null | undefined | boolean;
15
15
  type Children = Child | Child[];
16
16
  interface LightDeskIntrinsicElements {
17
17
  button: Props & {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcanejs/react-toolkit",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "private": false,
5
5
  "description": "Build web-accessible control interfaces for your long-running Node.js processes",
6
6
  "keywords": [
@@ -39,7 +39,6 @@
39
39
  "@types/react": "^18",
40
40
  "@types/react-reconciler": "^0.28.8",
41
41
  "eslint": "^8.57.0",
42
- "react-reconciler": "^0.28.0",
43
42
  "tsup": "^8.1.0",
44
43
  "typescript": "^5.3.3",
45
44
  "zod": "^3.23.8",
@@ -49,6 +48,7 @@
49
48
  "dependencies": {
50
49
  "lodash": "^4.17.21",
51
50
  "react": "^18",
51
+ "react-reconciler": "0.28.0",
52
52
  "@arcanejs/toolkit": "^0.2.1"
53
53
  },
54
54
  "peerDependencies": {