boma 2.0.5 → 2.1.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/async.d.ts +4 -0
- package/dist/async.js +119 -0
- package/dist/boma.d.ts +11 -0
- package/dist/boma.js +123 -0
- package/dist/{index.d.ts → common.d.ts} +25 -3
- package/dist/{index.js → common.js} +8 -126
- package/package.json +12 -11
- package/readme.md +403 -35
package/dist/async.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { addToJSONProps, ReadJSONProps, ReadJSONResult, SaveJSONProps } from './common.js';
|
|
2
|
+
export declare const saveJSONAsync: (saveInput: SaveJSONProps) => Promise<void>;
|
|
3
|
+
export declare const readJSONAsync: <T = any>(props: ReadJSONProps) => Promise<ReadJSONResult<T>>;
|
|
4
|
+
export declare const addToJSONAsync: (saveInput: addToJSONProps) => Promise<void>;
|
package/dist/async.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { readFile, writeFile } from 'fs/promises';
|
|
2
|
+
import { getDate, getIssueMessage, getSerializationIssues, isErrorWithCode, isPlainMergeableObject, isSyntaxError, logSerializationIssues, sanitizeNonSerializable, } from './common.js';
|
|
3
|
+
export const saveJSONAsync = async (saveInput) => {
|
|
4
|
+
const { filePath, objToSave, format = false, logSaving = false, replaceNonSerializable = false, silent = true, } = saveInput;
|
|
5
|
+
const issues = getSerializationIssues(objToSave);
|
|
6
|
+
if (issues.length > 0) {
|
|
7
|
+
!silent && logSerializationIssues(issues);
|
|
8
|
+
if (!replaceNonSerializable && !silent) {
|
|
9
|
+
const firstIssue = issues[0];
|
|
10
|
+
const errorMessage = `[${getDate()}] Boma got non JSON-serializable object at saveJSON! ${getIssueMessage(firstIssue)}`;
|
|
11
|
+
console.error(errorMessage);
|
|
12
|
+
throw new Error(errorMessage);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
const valueToSave = replaceNonSerializable && issues.length > 0
|
|
16
|
+
? sanitizeNonSerializable(objToSave)
|
|
17
|
+
: objToSave;
|
|
18
|
+
try {
|
|
19
|
+
const json = format ? JSON.stringify(valueToSave, null, 2) : JSON.stringify(valueToSave);
|
|
20
|
+
await writeFile(filePath, json, 'utf8');
|
|
21
|
+
!silent && logSaving && console.log(`[${getDate()}] Write file ${filePath} successfully`);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
if (isErrorWithCode(error)) {
|
|
25
|
+
console.error(`[${getDate()}] Boma got filesystem error for ${filePath}:`, error);
|
|
26
|
+
}
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
export const readJSONAsync = async (props) => {
|
|
31
|
+
const { filePath, createIfNotFound = false, parseJSON = true, silent = true } = props;
|
|
32
|
+
try {
|
|
33
|
+
const savedfile = await readFile(filePath, 'utf8');
|
|
34
|
+
if (parseJSON) {
|
|
35
|
+
return savedfile.trim() === '' ? null : JSON.parse(savedfile);
|
|
36
|
+
}
|
|
37
|
+
return savedfile;
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
if (isErrorWithCode(err)) {
|
|
41
|
+
switch (err.code) {
|
|
42
|
+
case 'ENOENT':
|
|
43
|
+
if (createIfNotFound) {
|
|
44
|
+
!silent && console.log('Try to create: ', filePath);
|
|
45
|
+
try {
|
|
46
|
+
const initialContent = typeof createIfNotFound === 'boolean' ? '{}' : JSON.stringify(createIfNotFound);
|
|
47
|
+
await writeFile(filePath, initialContent, 'utf8');
|
|
48
|
+
}
|
|
49
|
+
catch (writeErr) {
|
|
50
|
+
console.error(`Error creating file ${filePath}: `, writeErr, '\n');
|
|
51
|
+
}
|
|
52
|
+
return (typeof createIfNotFound === 'boolean' ? {} : createIfNotFound);
|
|
53
|
+
}
|
|
54
|
+
console.info("Boma can't find file: ", filePath);
|
|
55
|
+
return parseJSON ? {} : null;
|
|
56
|
+
case 'EACCES':
|
|
57
|
+
console.error(`Access denied for ${filePath}`);
|
|
58
|
+
return null;
|
|
59
|
+
default:
|
|
60
|
+
console.error(`Some filesystem error when try readJSON: ${filePath}`);
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (isSyntaxError(err)) {
|
|
65
|
+
console.error('File ', filePath, ' has incorrect JSON syntax', '\n');
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
console.error('Function readJSON error:', err, '\n');
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
export const addToJSONAsync = async (saveInput) => {
|
|
73
|
+
const { filePath, dataToAdd, format = false, logSaving = false, replaceNonSerializable = false, silent = true, } = saveInput;
|
|
74
|
+
const oldJSON = await readJSONAsync({ filePath, createIfNotFound: true, silent });
|
|
75
|
+
if (oldJSON === null || typeof oldJSON !== 'object') {
|
|
76
|
+
return saveJSONAsync({
|
|
77
|
+
filePath,
|
|
78
|
+
objToSave: dataToAdd,
|
|
79
|
+
format,
|
|
80
|
+
logSaving,
|
|
81
|
+
replaceNonSerializable,
|
|
82
|
+
silent
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
if (Array.isArray(oldJSON) && Array.isArray(dataToAdd)) {
|
|
86
|
+
await saveJSONAsync({
|
|
87
|
+
filePath,
|
|
88
|
+
objToSave: [...oldJSON, ...dataToAdd],
|
|
89
|
+
format,
|
|
90
|
+
logSaving,
|
|
91
|
+
replaceNonSerializable,
|
|
92
|
+
silent
|
|
93
|
+
});
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (isPlainMergeableObject(oldJSON) && isPlainMergeableObject(dataToAdd)) {
|
|
97
|
+
await saveJSONAsync({
|
|
98
|
+
filePath,
|
|
99
|
+
objToSave: { ...oldJSON, ...dataToAdd },
|
|
100
|
+
format,
|
|
101
|
+
logSaving,
|
|
102
|
+
replaceNonSerializable,
|
|
103
|
+
silent
|
|
104
|
+
});
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (Array.isArray(dataToAdd) && isPlainMergeableObject(oldJSON) && Object.keys(oldJSON).length === 0) {
|
|
108
|
+
await saveJSONAsync({
|
|
109
|
+
filePath,
|
|
110
|
+
objToSave: [...dataToAdd],
|
|
111
|
+
format,
|
|
112
|
+
logSaving,
|
|
113
|
+
replaceNonSerializable,
|
|
114
|
+
silent
|
|
115
|
+
});
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
throw new Error('Cannot merge array with object');
|
|
119
|
+
};
|
package/dist/boma.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { AddToJSONAsyncProps, addToJSONProps, AddToJSONSyncProps, ReadJSONAsyncProps, ReadJSONProps, ReadJSONResult, ReadJSONSyncProps, SaveJSONAsyncProps, SaveJSONProps, SaveJSONSyncProps } from './common.js';
|
|
2
|
+
export * from './common.js';
|
|
3
|
+
export declare function saveJSON(props: SaveJSONAsyncProps): Promise<void>;
|
|
4
|
+
export declare function saveJSON(props: SaveJSONSyncProps): void;
|
|
5
|
+
export declare function saveJSON(props: SaveJSONProps): void | Promise<void>;
|
|
6
|
+
export declare function readJSON<T = any>(props: ReadJSONAsyncProps): Promise<ReadJSONResult<T>>;
|
|
7
|
+
export declare function readJSON<T = any>(props: ReadJSONSyncProps): ReadJSONResult<T>;
|
|
8
|
+
export declare function readJSON<T = any>(props: ReadJSONProps): ReadJSONResult<T> | Promise<ReadJSONResult<T>>;
|
|
9
|
+
export declare function addToJSON(props: AddToJSONAsyncProps): Promise<void>;
|
|
10
|
+
export declare function addToJSON(props: AddToJSONSyncProps): void;
|
|
11
|
+
export declare function addToJSON(props: addToJSONProps): void | Promise<void>;
|
package/dist/boma.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from 'fs';
|
|
2
|
+
import { addToJSONAsync, readJSONAsync, saveJSONAsync } from './async.js';
|
|
3
|
+
import { getDate, getIssueMessage, getSerializationIssues, isErrorWithCode, isPlainMergeableObject, isSyntaxError, logSerializationIssues, sanitizeNonSerializable } from './common.js';
|
|
4
|
+
export * from './common.js';
|
|
5
|
+
const saveJSONSync = (saveInput) => {
|
|
6
|
+
const { filePath, objToSave, format = false, logSaving = false, replaceNonSerializable = false, silent = true, } = saveInput;
|
|
7
|
+
const issues = getSerializationIssues(objToSave);
|
|
8
|
+
if (issues.length > 0) {
|
|
9
|
+
!silent && logSerializationIssues(issues);
|
|
10
|
+
if (!replaceNonSerializable && !silent) {
|
|
11
|
+
const firstIssue = issues[0];
|
|
12
|
+
const errorMessage = `[${getDate()}] Boma got non JSON-serializable object at saveJSON! ${getIssueMessage(firstIssue)}`;
|
|
13
|
+
console.error(errorMessage);
|
|
14
|
+
throw new Error(errorMessage);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
const valueToSave = replaceNonSerializable && issues.length > 0
|
|
18
|
+
? sanitizeNonSerializable(objToSave)
|
|
19
|
+
: objToSave;
|
|
20
|
+
try {
|
|
21
|
+
const json = format ? JSON.stringify(valueToSave, null, 2) : JSON.stringify(valueToSave);
|
|
22
|
+
writeFileSync(filePath, json, 'utf8');
|
|
23
|
+
!silent && logSaving && console.log(`[${getDate()}] Write file ${filePath} successfully`);
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
if (isErrorWithCode(error)) {
|
|
27
|
+
console.error(`[${getDate()}] Boma got filesystem error for ${filePath}:`, error);
|
|
28
|
+
}
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
const readJSONSync = (props) => {
|
|
33
|
+
const { filePath, createIfNotFound = false, parseJSON = true, silent = true } = props;
|
|
34
|
+
try {
|
|
35
|
+
const savedfile = readFileSync(filePath, 'utf8');
|
|
36
|
+
if (parseJSON) {
|
|
37
|
+
return savedfile.trim() === '' ? null : JSON.parse(savedfile);
|
|
38
|
+
}
|
|
39
|
+
return savedfile;
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
if (isErrorWithCode(err)) {
|
|
43
|
+
switch (err.code) {
|
|
44
|
+
case 'ENOENT':
|
|
45
|
+
if (createIfNotFound) {
|
|
46
|
+
!silent && console.log('Try to create: ', filePath);
|
|
47
|
+
try {
|
|
48
|
+
const initialContent = typeof createIfNotFound === 'boolean' ? '{}' : JSON.stringify(createIfNotFound);
|
|
49
|
+
writeFileSync(filePath, initialContent, 'utf8');
|
|
50
|
+
}
|
|
51
|
+
catch (writeErr) {
|
|
52
|
+
console.error(`Error creating file ${filePath}: `, writeErr, '\n');
|
|
53
|
+
}
|
|
54
|
+
return (typeof createIfNotFound === 'boolean' ? {} : createIfNotFound);
|
|
55
|
+
}
|
|
56
|
+
console.info("Boma can't find file: ", filePath);
|
|
57
|
+
return parseJSON ? {} : null;
|
|
58
|
+
case 'EACCES':
|
|
59
|
+
console.error(`Access denied for ${filePath}`);
|
|
60
|
+
return null;
|
|
61
|
+
default:
|
|
62
|
+
console.error(`Some filesystem error when try readJSON: ${filePath}`);
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (isSyntaxError(err)) {
|
|
67
|
+
console.error('File ', filePath, ' has incorrect JSON syntax', '\n');
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
console.error('Function readJSON error:', err, '\n');
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
const addToJSONSync = (saveInput) => {
|
|
75
|
+
const { filePath, dataToAdd, format = false, logSaving = false, replaceNonSerializable = false, silent = true, } = saveInput;
|
|
76
|
+
const oldJSON = readJSONSync({ filePath, createIfNotFound: true, silent });
|
|
77
|
+
if (oldJSON === null || typeof oldJSON !== 'object') {
|
|
78
|
+
return saveJSONSync({ filePath, objToSave: dataToAdd, format, logSaving, replaceNonSerializable, silent });
|
|
79
|
+
}
|
|
80
|
+
if (Array.isArray(oldJSON) && Array.isArray(dataToAdd)) {
|
|
81
|
+
saveJSONSync({
|
|
82
|
+
filePath,
|
|
83
|
+
objToSave: [...oldJSON, ...dataToAdd],
|
|
84
|
+
format,
|
|
85
|
+
logSaving,
|
|
86
|
+
replaceNonSerializable,
|
|
87
|
+
silent
|
|
88
|
+
});
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (isPlainMergeableObject(oldJSON) && isPlainMergeableObject(dataToAdd)) {
|
|
92
|
+
saveJSONSync({
|
|
93
|
+
filePath,
|
|
94
|
+
objToSave: { ...oldJSON, ...dataToAdd },
|
|
95
|
+
format,
|
|
96
|
+
logSaving,
|
|
97
|
+
replaceNonSerializable,
|
|
98
|
+
silent
|
|
99
|
+
});
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (Array.isArray(dataToAdd) && isPlainMergeableObject(oldJSON) && Object.keys(oldJSON).length === 0) {
|
|
103
|
+
saveJSONSync({
|
|
104
|
+
filePath,
|
|
105
|
+
objToSave: [...dataToAdd],
|
|
106
|
+
format,
|
|
107
|
+
logSaving,
|
|
108
|
+
replaceNonSerializable,
|
|
109
|
+
silent
|
|
110
|
+
});
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
throw new Error('Cannot merge array with object');
|
|
114
|
+
};
|
|
115
|
+
export function saveJSON(props) {
|
|
116
|
+
return props.async === true ? saveJSONAsync(props) : saveJSONSync(props);
|
|
117
|
+
}
|
|
118
|
+
export function readJSON(props) {
|
|
119
|
+
return props.async === true ? readJSONAsync(props) : readJSONSync(props);
|
|
120
|
+
}
|
|
121
|
+
export function addToJSON(props) {
|
|
122
|
+
return props.async === true ? addToJSONAsync(props) : addToJSONSync(props);
|
|
123
|
+
}
|
|
@@ -11,6 +11,7 @@ export interface ReadJSONProps {
|
|
|
11
11
|
parseJSON?: boolean;
|
|
12
12
|
createIfNotFound?: boolean | SerializableObject | SerializableArray;
|
|
13
13
|
silent?: boolean;
|
|
14
|
+
async?: boolean;
|
|
14
15
|
}
|
|
15
16
|
export interface SaveJSONProps {
|
|
16
17
|
filePath: string;
|
|
@@ -19,6 +20,7 @@ export interface SaveJSONProps {
|
|
|
19
20
|
logSaving?: boolean;
|
|
20
21
|
replaceNonSerializable?: boolean;
|
|
21
22
|
silent?: boolean;
|
|
23
|
+
async?: boolean;
|
|
22
24
|
}
|
|
23
25
|
export interface addToJSONProps {
|
|
24
26
|
filePath: string;
|
|
@@ -27,7 +29,27 @@ export interface addToJSONProps {
|
|
|
27
29
|
logSaving?: boolean;
|
|
28
30
|
replaceNonSerializable?: boolean;
|
|
29
31
|
silent?: boolean;
|
|
32
|
+
async?: boolean;
|
|
30
33
|
}
|
|
34
|
+
export type ReadJSONSyncProps = ReadJSONProps & {
|
|
35
|
+
async?: false;
|
|
36
|
+
};
|
|
37
|
+
export type ReadJSONAsyncProps = ReadJSONProps & {
|
|
38
|
+
async: true;
|
|
39
|
+
};
|
|
40
|
+
export type SaveJSONSyncProps = SaveJSONProps & {
|
|
41
|
+
async?: false;
|
|
42
|
+
};
|
|
43
|
+
export type SaveJSONAsyncProps = SaveJSONProps & {
|
|
44
|
+
async: true;
|
|
45
|
+
};
|
|
46
|
+
export type AddToJSONSyncProps = addToJSONProps & {
|
|
47
|
+
async?: false;
|
|
48
|
+
};
|
|
49
|
+
export type AddToJSONAsyncProps = addToJSONProps & {
|
|
50
|
+
async: true;
|
|
51
|
+
};
|
|
52
|
+
export type ReadJSONResult<T = any> = T | string | null;
|
|
31
53
|
export type ErrorWithCode = Error & {
|
|
32
54
|
code: string;
|
|
33
55
|
};
|
|
@@ -44,12 +66,12 @@ export interface SerializationIssue {
|
|
|
44
66
|
export declare const isErrorWithCode: (error: unknown) => error is ErrorWithCode;
|
|
45
67
|
export declare const isErrorWithMessage: (error: unknown) => error is ErrorWithMessage;
|
|
46
68
|
export declare const isSyntaxError: (error: unknown) => error is SyntaxError;
|
|
69
|
+
export declare const getDate: () => string;
|
|
47
70
|
export declare const isObjectLike: (value: unknown) => value is object;
|
|
48
71
|
export declare const isPlainMergeableObject: (value: unknown) => value is Record<string, unknown>;
|
|
72
|
+
export declare const getIssueMessage: (issue: SerializationIssue) => string;
|
|
49
73
|
export declare const getSerializationIssues: (value: unknown, path?: string, ancestors?: WeakMap<object, string>, issues?: SerializationIssue[]) => SerializationIssue[];
|
|
50
74
|
export declare const isSerializable: (value: unknown) => value is Serializable;
|
|
51
75
|
export declare const sanitizeNonSerializable: (value: unknown, path?: string, ancestors?: WeakMap<object, string>) => Serializable;
|
|
52
|
-
export declare const
|
|
53
|
-
export declare const readJSON: (props: ReadJSONProps) => any;
|
|
54
|
-
export declare const addToJSON: (saveInput: addToJSONProps) => void;
|
|
76
|
+
export declare const logSerializationIssues: (issues: SerializationIssue[]) => void;
|
|
55
77
|
export {};
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync } from 'fs';
|
|
2
1
|
export const isErrorWithCode = (error) => {
|
|
3
2
|
return error instanceof Error && 'code' in error;
|
|
4
3
|
};
|
|
@@ -8,7 +7,7 @@ export const isErrorWithMessage = (error) => {
|
|
|
8
7
|
export const isSyntaxError = (error) => {
|
|
9
8
|
return error instanceof SyntaxError;
|
|
10
9
|
};
|
|
11
|
-
const getDate = () => {
|
|
10
|
+
export const getDate = () => {
|
|
12
11
|
const castedDate = new Date();
|
|
13
12
|
return castedDate.toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit' });
|
|
14
13
|
};
|
|
@@ -26,7 +25,7 @@ const pathJoin = (basePath, key) => {
|
|
|
26
25
|
? `${basePath}.${key}`
|
|
27
26
|
: `${basePath}[${JSON.stringify(key)}]`;
|
|
28
27
|
};
|
|
29
|
-
const getIssueMessage = (issue) => {
|
|
28
|
+
export const getIssueMessage = (issue) => {
|
|
30
29
|
if (issue.kind === 'circular') {
|
|
31
30
|
return `Field "${issue.path}" contains circular reference${issue.circularTo ? ` to "${issue.circularTo}"` : ''}`;
|
|
32
31
|
}
|
|
@@ -144,16 +143,16 @@ const typeFlagFromValue = (value) => {
|
|
|
144
143
|
return String(t);
|
|
145
144
|
};
|
|
146
145
|
export const sanitizeNonSerializable = (value, path = '$', ancestors = new WeakMap()) => {
|
|
147
|
-
if (value === null
|
|
146
|
+
if (value === null) {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
if (typeof value === 'string' || typeof value === 'boolean') {
|
|
148
150
|
return value;
|
|
149
151
|
}
|
|
150
152
|
if (typeof value === 'number') {
|
|
151
153
|
return Number.isFinite(value) ? value : 'non-finite-number';
|
|
152
154
|
}
|
|
153
|
-
if (
|
|
154
|
-
typeof value === 'function' ||
|
|
155
|
-
typeof value === 'symbol' ||
|
|
156
|
-
typeof value === 'bigint') {
|
|
155
|
+
if (['undefined', 'function', 'symbol', 'bigint'].includes(typeof value)) {
|
|
157
156
|
return typeFlagFromValue(value);
|
|
158
157
|
}
|
|
159
158
|
if (Array.isArray(value)) {
|
|
@@ -179,125 +178,8 @@ export const sanitizeNonSerializable = (value, path = '$', ancestors = new WeakM
|
|
|
179
178
|
}
|
|
180
179
|
return typeFlagFromValue(value);
|
|
181
180
|
};
|
|
182
|
-
const logSerializationIssues = (issues) => {
|
|
181
|
+
export const logSerializationIssues = (issues) => {
|
|
183
182
|
for (const issue of issues) {
|
|
184
183
|
console.info(`[${getDate()}] Boma serialization info: ${getIssueMessage(issue)}`);
|
|
185
184
|
}
|
|
186
185
|
};
|
|
187
|
-
export const saveJSON = (saveInput) => {
|
|
188
|
-
const { filePath, objToSave, format = false, logSaving = false, replaceNonSerializable = false, silent = true, } = saveInput;
|
|
189
|
-
const issues = getSerializationIssues(objToSave);
|
|
190
|
-
if (issues.length > 0) {
|
|
191
|
-
logSerializationIssues(issues);
|
|
192
|
-
if (!replaceNonSerializable) {
|
|
193
|
-
const firstIssue = issues[0];
|
|
194
|
-
const errorMessage = `[${getDate()}] Boma got non JSON-serializable object at saveJSON! ${getIssueMessage(firstIssue)}`;
|
|
195
|
-
console.error(errorMessage);
|
|
196
|
-
throw new Error(errorMessage);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
const valueToSave = replaceNonSerializable && issues.length > 0
|
|
200
|
-
? sanitizeNonSerializable(objToSave)
|
|
201
|
-
: objToSave;
|
|
202
|
-
try {
|
|
203
|
-
const json = format ? JSON.stringify(valueToSave, null, 2) : JSON.stringify(valueToSave);
|
|
204
|
-
writeFileSync(filePath, json, 'utf8');
|
|
205
|
-
!silent && logSaving && console.log(`[${getDate()}] Write file ${filePath} successfully`);
|
|
206
|
-
}
|
|
207
|
-
catch (error) {
|
|
208
|
-
if (isErrorWithCode(error)) {
|
|
209
|
-
console.error(`[${getDate()}] Boma got filesystem error for ${filePath}:`, error);
|
|
210
|
-
}
|
|
211
|
-
throw error;
|
|
212
|
-
}
|
|
213
|
-
};
|
|
214
|
-
export const readJSON = (props) => {
|
|
215
|
-
const { filePath, createIfNotFound = false, parseJSON = true, silent = true } = props;
|
|
216
|
-
try {
|
|
217
|
-
const savedfile = readFileSync(filePath, 'utf8');
|
|
218
|
-
if (parseJSON) {
|
|
219
|
-
return savedfile.trim() === '' ? null : JSON.parse(savedfile);
|
|
220
|
-
}
|
|
221
|
-
return savedfile;
|
|
222
|
-
}
|
|
223
|
-
catch (err) {
|
|
224
|
-
if (isErrorWithCode(err)) {
|
|
225
|
-
switch (err.code) {
|
|
226
|
-
case 'ENOENT':
|
|
227
|
-
if (createIfNotFound) {
|
|
228
|
-
!silent && console.log('Try to create: ', filePath);
|
|
229
|
-
try {
|
|
230
|
-
const initialContent = typeof createIfNotFound === 'boolean' ? '{}' : JSON.stringify(createIfNotFound);
|
|
231
|
-
writeFileSync(filePath, initialContent, 'utf8');
|
|
232
|
-
}
|
|
233
|
-
catch (writeErr) {
|
|
234
|
-
console.error(`Error creating file ${filePath}: `, writeErr, '\n');
|
|
235
|
-
}
|
|
236
|
-
return typeof createIfNotFound === 'boolean' ? {} : createIfNotFound;
|
|
237
|
-
}
|
|
238
|
-
console.info("Boma can't find file: ", filePath);
|
|
239
|
-
return parseJSON ? {} : null;
|
|
240
|
-
case 'EACCES':
|
|
241
|
-
console.error(`Access denied for ${filePath}`);
|
|
242
|
-
return null;
|
|
243
|
-
default:
|
|
244
|
-
console.error(`Some filesystem error when try readJSON: ${filePath}`);
|
|
245
|
-
return null;
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
if (isSyntaxError(err)) {
|
|
249
|
-
console.error('File ', filePath, ' has incorrect JSON syntax', '\n');
|
|
250
|
-
return null;
|
|
251
|
-
}
|
|
252
|
-
console.error('Function readJSON error:', err, '\n');
|
|
253
|
-
return null;
|
|
254
|
-
}
|
|
255
|
-
};
|
|
256
|
-
export const addToJSON = (saveInput) => {
|
|
257
|
-
const { filePath, dataToAdd, format = false, logSaving = false, replaceNonSerializable = false, silent = true, } = saveInput;
|
|
258
|
-
const oldJSON = readJSON({ filePath, createIfNotFound: true, silent });
|
|
259
|
-
if (oldJSON === null || typeof oldJSON !== 'object') {
|
|
260
|
-
return saveJSON({
|
|
261
|
-
filePath,
|
|
262
|
-
objToSave: dataToAdd,
|
|
263
|
-
format,
|
|
264
|
-
logSaving,
|
|
265
|
-
replaceNonSerializable,
|
|
266
|
-
silent
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
if (Array.isArray(oldJSON) && Array.isArray(dataToAdd)) {
|
|
270
|
-
saveJSON({
|
|
271
|
-
filePath,
|
|
272
|
-
objToSave: [...oldJSON, ...dataToAdd],
|
|
273
|
-
format,
|
|
274
|
-
logSaving,
|
|
275
|
-
replaceNonSerializable,
|
|
276
|
-
silent
|
|
277
|
-
});
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
if (isPlainMergeableObject(oldJSON) && isPlainMergeableObject(dataToAdd)) {
|
|
281
|
-
saveJSON({
|
|
282
|
-
filePath,
|
|
283
|
-
objToSave: { ...oldJSON, ...dataToAdd },
|
|
284
|
-
format,
|
|
285
|
-
logSaving,
|
|
286
|
-
replaceNonSerializable,
|
|
287
|
-
silent
|
|
288
|
-
});
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
if (Array.isArray(dataToAdd) && isPlainMergeableObject(oldJSON) && Object.keys(oldJSON).length === 0) {
|
|
292
|
-
saveJSON({
|
|
293
|
-
filePath,
|
|
294
|
-
objToSave: [...dataToAdd],
|
|
295
|
-
format,
|
|
296
|
-
logSaving,
|
|
297
|
-
replaceNonSerializable,
|
|
298
|
-
silent
|
|
299
|
-
});
|
|
300
|
-
return;
|
|
301
|
-
}
|
|
302
|
-
throw new Error('Cannot merge array with object');
|
|
303
|
-
};
|
package/package.json
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "boma",
|
|
3
|
-
"version": "2.0
|
|
4
|
-
"description": "
|
|
5
|
-
"main": "./dist/
|
|
6
|
-
"types": "./dist/
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"description": "Simple JSON helper",
|
|
5
|
+
"main": "./dist/boma.js",
|
|
6
|
+
"types": "./dist/boma.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/boma.d.ts",
|
|
10
|
+
"import": "./dist/boma.js",
|
|
11
|
+
"default": "./dist/boma.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
7
14
|
"type": "module",
|
|
8
15
|
"scripts": {
|
|
9
16
|
"build": "tsc",
|
|
@@ -13,13 +20,7 @@
|
|
|
13
20
|
"type": "git",
|
|
14
21
|
"url": "git+https://github.com/Psychosynthesis/Boma.git"
|
|
15
22
|
},
|
|
16
|
-
"
|
|
17
|
-
".": {
|
|
18
|
-
"types": "./dist/index.d.ts",
|
|
19
|
-
"import": "./dist/index.js",
|
|
20
|
-
"default": "./dist/index.js"
|
|
21
|
-
}
|
|
22
|
-
},
|
|
23
|
+
"files": [ "dist", "readme.md", "LICENSE" ],
|
|
23
24
|
"author": "Nick G.",
|
|
24
25
|
"license": "MIT",
|
|
25
26
|
"bugs": {
|
package/readme.md
CHANGED
|
@@ -1,88 +1,456 @@
|
|
|
1
1
|
# Boma JSON helper
|
|
2
|
-
Super-simple helper for working with JSON
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
Super-simple helper for reading, saving and updating JSON files.
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
Works in synchronous mode by default.
|
|
6
|
+
Pass `async: true` to use the asynchronous implementation.
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Old synchronous calls remain unchanged.
|
|
9
|
+
|
|
10
|
+
# Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install boma
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
# Import
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import { readJSON, saveJSON, addToJSON } from 'boma';
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
# Synchronous usage
|
|
23
|
+
|
|
24
|
+
Without the `async` option, all functions work synchronously.
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
9
27
|
const logs = readJSON({
|
|
10
|
-
filePath: '/support.
|
|
28
|
+
filePath: '/support.json',
|
|
11
29
|
createIfNotFound: {},
|
|
12
30
|
parseJSON: true,
|
|
13
|
-
silent: true, // Do not log warnings
|
|
31
|
+
silent: true, // Do not log warnings
|
|
14
32
|
});
|
|
33
|
+
|
|
15
34
|
/*
|
|
16
35
|
logs = {
|
|
17
|
-
any: {},
|
|
36
|
+
any: {},
|
|
18
37
|
key: {}
|
|
19
38
|
}
|
|
20
39
|
*/
|
|
40
|
+
```
|
|
21
41
|
|
|
42
|
+
```typescript
|
|
22
43
|
saveJSON({
|
|
23
44
|
filePath: '/test.json',
|
|
24
|
-
objToSave: {
|
|
45
|
+
objToSave: {
|
|
46
|
+
any: {},
|
|
47
|
+
key: {},
|
|
48
|
+
},
|
|
25
49
|
format: true,
|
|
26
50
|
logSaving: false,
|
|
27
|
-
silent: true,
|
|
28
|
-
});
|
|
29
|
-
|
|
51
|
+
silent: true,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
/*
|
|
55
|
+
Will save:
|
|
56
|
+
|
|
30
57
|
{
|
|
31
|
-
"any": {},
|
|
58
|
+
"any": {},
|
|
32
59
|
"key": {}
|
|
33
60
|
}
|
|
34
61
|
*/
|
|
62
|
+
```
|
|
35
63
|
|
|
64
|
+
```typescript
|
|
36
65
|
addToJSON({
|
|
37
|
-
filePath: '/support.
|
|
38
|
-
dataToAdd: {
|
|
66
|
+
filePath: '/support.json',
|
|
67
|
+
dataToAdd: {
|
|
68
|
+
'6sdf89g7dghg': {
|
|
69
|
+
any: 'data',
|
|
70
|
+
someFunc: () => {},
|
|
71
|
+
},
|
|
72
|
+
},
|
|
39
73
|
format: false,
|
|
40
74
|
logSaving: false,
|
|
41
|
-
replaceNonSerializable: true
|
|
75
|
+
replaceNonSerializable: true,
|
|
42
76
|
});
|
|
43
|
-
|
|
44
|
-
|
|
77
|
+
|
|
78
|
+
/*
|
|
79
|
+
Will save:
|
|
80
|
+
|
|
81
|
+
{
|
|
82
|
+
"6sdf89g7dghg": {
|
|
83
|
+
"any": "data",
|
|
84
|
+
"someFunc": "function"
|
|
85
|
+
}
|
|
86
|
+
}
|
|
45
87
|
*/
|
|
46
88
|
```
|
|
47
89
|
|
|
48
|
-
|
|
90
|
+
# Asynchronous usage
|
|
91
|
+
|
|
92
|
+
Pass `async: true` to any main function.
|
|
93
|
+
|
|
94
|
+
In this mode:
|
|
95
|
+
|
|
96
|
+
* `readJSON` returns a `Promise` with the read value;
|
|
97
|
+
* `saveJSON` returns `Promise<void>`;
|
|
98
|
+
* `addToJSON` returns `Promise<void>`.
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
const logs = await readJSON({
|
|
102
|
+
filePath: '/support.json',
|
|
103
|
+
createIfNotFound: {},
|
|
104
|
+
parseJSON: true,
|
|
105
|
+
silent: true,
|
|
106
|
+
async: true,
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
await saveJSON({
|
|
112
|
+
filePath: '/test.json',
|
|
113
|
+
objToSave: {
|
|
114
|
+
any: {},
|
|
115
|
+
key: {},
|
|
116
|
+
},
|
|
117
|
+
format: true,
|
|
118
|
+
logSaving: false,
|
|
119
|
+
silent: true,
|
|
120
|
+
async: true,
|
|
121
|
+
});
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
await addToJSON({
|
|
126
|
+
filePath: '/support.json',
|
|
127
|
+
dataToAdd: {
|
|
128
|
+
newKey: {
|
|
129
|
+
any: 'data',
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
format: true,
|
|
133
|
+
silent: true,
|
|
134
|
+
async: true,
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The same functions are used in both modes. Separate async imports are not required.
|
|
139
|
+
|
|
140
|
+
# Reading JSON
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
const result = readJSON({
|
|
144
|
+
filePath: '/test.json',
|
|
145
|
+
});
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Default options:
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
{
|
|
152
|
+
parseJSON: true,
|
|
153
|
+
createIfNotFound: false,
|
|
154
|
+
silent: true,
|
|
155
|
+
async: false
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
When `parseJSON` is `true`, file content is parsed using `JSON.parse`.
|
|
160
|
+
|
|
161
|
+
When `parseJSON` is `false`, raw file content is returned as a string.
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
const rawContent = readJSON({
|
|
165
|
+
filePath: '/test.json',
|
|
166
|
+
parseJSON: false,
|
|
167
|
+
});
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
When `createIfNotFound` is `true`, a missing file is created with an empty object:
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
const result = readJSON({
|
|
174
|
+
filePath: '/test.json',
|
|
175
|
+
createIfNotFound: true,
|
|
176
|
+
});
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
You can also provide the initial object or array:
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
const result = readJSON({
|
|
183
|
+
filePath: '/test.json',
|
|
184
|
+
createIfNotFound: {
|
|
185
|
+
created: true,
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
# Saving JSON
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
saveJSON({
|
|
194
|
+
filePath: '/test.json',
|
|
195
|
+
objToSave: {
|
|
196
|
+
any: 'data',
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Use `format: true` to save formatted JSON with indentation and line breaks:
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
saveJSON({
|
|
205
|
+
filePath: '/test.json',
|
|
206
|
+
objToSave: {
|
|
207
|
+
any: 'data',
|
|
208
|
+
},
|
|
209
|
+
format: true,
|
|
210
|
+
});
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Use `logSaving: true` to log successful file saving when `silent` is `false`.
|
|
214
|
+
|
|
215
|
+
# Non-serializable values
|
|
216
|
+
|
|
217
|
+
JSON does not support values such as:
|
|
218
|
+
|
|
219
|
+
* `undefined`;
|
|
220
|
+
* functions;
|
|
221
|
+
* symbols;
|
|
222
|
+
* `bigint`;
|
|
223
|
+
* `NaN` and `Infinity`;
|
|
224
|
+
* circular references.
|
|
225
|
+
|
|
226
|
+
Use `replaceNonSerializable: true` to replace such values with string type flags:
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
saveJSON({
|
|
230
|
+
filePath: '/test.json',
|
|
231
|
+
objToSave: {
|
|
232
|
+
callback: () => {},
|
|
233
|
+
missing: undefined,
|
|
234
|
+
largeNumber: BigInt(10),
|
|
235
|
+
invalidNumber: NaN,
|
|
236
|
+
},
|
|
237
|
+
replaceNonSerializable: true,
|
|
238
|
+
format: true,
|
|
239
|
+
});
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Will save:
|
|
243
|
+
|
|
244
|
+
```json
|
|
245
|
+
{
|
|
246
|
+
"callback": "function",
|
|
247
|
+
"missing": "undefined",
|
|
248
|
+
"largeNumber": "bigint",
|
|
249
|
+
"invalidNumber": "non-finite-number"
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Circular references are replaced with `"circular"`.
|
|
254
|
+
|
|
255
|
+
# Adding data to JSON
|
|
256
|
+
|
|
257
|
+
`addToJSON` reads the existing file, merges the new data and saves the result.
|
|
258
|
+
|
|
259
|
+
Objects are shallow merged:
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
// Existing file:
|
|
263
|
+
{
|
|
264
|
+
"first": 1,
|
|
265
|
+
"second": 2
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
addToJSON({
|
|
271
|
+
filePath: '/test.json',
|
|
272
|
+
dataToAdd: {
|
|
273
|
+
second: 20,
|
|
274
|
+
third: 3,
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Result:
|
|
280
|
+
|
|
281
|
+
```json
|
|
282
|
+
{
|
|
283
|
+
"first": 1,
|
|
284
|
+
"second": 20,
|
|
285
|
+
"third": 3
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
Existing object keys are overwritten by values from `dataToAdd`.
|
|
290
|
+
|
|
291
|
+
Arrays are concatenated:
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
// Existing file:
|
|
295
|
+
[1, 2]
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
addToJSON({
|
|
300
|
+
filePath: '/test.json',
|
|
301
|
+
dataToAdd: [3, 4],
|
|
302
|
+
});
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
Result:
|
|
306
|
+
|
|
307
|
+
```json
|
|
308
|
+
[1, 2, 3, 4]
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
An object cannot be merged with an array. In this case, `addToJSON` throws:
|
|
312
|
+
|
|
313
|
+
```text
|
|
314
|
+
Cannot merge array with object
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
A missing file is created automatically.
|
|
318
|
+
|
|
319
|
+
# Typed reading
|
|
320
|
+
|
|
321
|
+
A result type can be passed to `readJSON`:
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
interface Config {
|
|
325
|
+
port: number;
|
|
326
|
+
production: boolean;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const config = readJSON<Config>({
|
|
330
|
+
filePath: '/config.json',
|
|
331
|
+
});
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
Async mode uses the same generic:
|
|
335
|
+
|
|
336
|
+
```typescript
|
|
337
|
+
const config = await readJSON<Config>({
|
|
338
|
+
filePath: '/config.json',
|
|
339
|
+
async: true,
|
|
340
|
+
});
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
Because `parseJSON: false` returns a string and reading errors can return `null`, the complete result type also includes `string | null`.
|
|
344
|
+
|
|
345
|
+
# Types
|
|
346
|
+
|
|
49
347
|
Main types:
|
|
50
348
|
|
|
51
349
|
```typescript
|
|
52
350
|
export type SerializablePrimitive = string | number | boolean | null;
|
|
53
351
|
export type SerializableArray = Serializable[];
|
|
54
|
-
export type SerializableObject = {
|
|
55
|
-
|
|
352
|
+
export type SerializableObject = {
|
|
353
|
+
[key: string]: Serializable;
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
export type Serializable =
|
|
357
|
+
| SerializablePrimitive
|
|
358
|
+
| SerializableArray
|
|
359
|
+
| SerializableObject;
|
|
56
360
|
|
|
57
361
|
export interface ReadJSONProps {
|
|
58
362
|
filePath: string;
|
|
59
363
|
parseJSON?: boolean;
|
|
60
|
-
createIfNotFound?:
|
|
364
|
+
createIfNotFound?:
|
|
365
|
+
| boolean
|
|
366
|
+
| SerializableObject
|
|
367
|
+
| SerializableArray;
|
|
368
|
+
silent?: boolean;
|
|
369
|
+
async?: boolean;
|
|
61
370
|
}
|
|
62
371
|
|
|
63
372
|
export interface SaveJSONProps {
|
|
64
373
|
filePath: string;
|
|
65
|
-
objToSave:
|
|
66
|
-
format?: boolean
|
|
374
|
+
objToSave: unknown;
|
|
375
|
+
format?: boolean;
|
|
67
376
|
logSaving?: boolean;
|
|
68
|
-
replaceNonSerializable?: boolean;
|
|
69
|
-
silent?: boolean;
|
|
377
|
+
replaceNonSerializable?: boolean;
|
|
378
|
+
silent?: boolean;
|
|
379
|
+
async?: boolean;
|
|
70
380
|
}
|
|
71
381
|
|
|
72
382
|
export interface addToJSONProps {
|
|
73
383
|
filePath: string;
|
|
74
|
-
dataToAdd:
|
|
75
|
-
|
|
384
|
+
dataToAdd:
|
|
385
|
+
| Record<string, unknown>
|
|
386
|
+
| unknown[];
|
|
387
|
+
format?: boolean;
|
|
76
388
|
logSaving?: boolean;
|
|
77
|
-
|
|
389
|
+
replaceNonSerializable?: boolean;
|
|
390
|
+
silent?: boolean;
|
|
391
|
+
async?: boolean;
|
|
78
392
|
}
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
Types used for synchronous and asynchronous overloads are also exported:
|
|
396
|
+
|
|
397
|
+
```typescript
|
|
398
|
+
export type ReadJSONSyncProps =
|
|
399
|
+
ReadJSONProps & { async?: false };
|
|
400
|
+
|
|
401
|
+
export type ReadJSONAsyncProps =
|
|
402
|
+
ReadJSONProps & { async: true };
|
|
403
|
+
|
|
404
|
+
export type SaveJSONSyncProps =
|
|
405
|
+
SaveJSONProps & { async?: false };
|
|
406
|
+
|
|
407
|
+
export type SaveJSONAsyncProps =
|
|
408
|
+
SaveJSONProps & { async: true };
|
|
409
|
+
|
|
410
|
+
export type AddToJSONSyncProps =
|
|
411
|
+
addToJSONProps & { async?: false };
|
|
412
|
+
|
|
413
|
+
export type AddToJSONAsyncProps =
|
|
414
|
+
addToJSONProps & { async: true };
|
|
415
|
+
|
|
416
|
+
export type ReadJSONResult<T = any> =
|
|
417
|
+
T | string | null;
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
# Helpers
|
|
79
421
|
|
|
80
|
-
|
|
81
|
-
export const isSerializable = (value: unknown) => boolean;
|
|
82
|
-
export const isObjectLike = (value: unknown) => boolean;
|
|
83
|
-
export const isPlainMergeableObject = (value: unknown) => boolean;
|
|
422
|
+
The package also exports serialization and type-checking helpers:
|
|
84
423
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
424
|
+
```typescript
|
|
425
|
+
isSerializable(value);
|
|
426
|
+
sanitizeNonSerializable(value);
|
|
427
|
+
getSerializationIssues(value);
|
|
428
|
+
|
|
429
|
+
isObjectLike(value);
|
|
430
|
+
isPlainMergeableObject(value);
|
|
431
|
+
|
|
432
|
+
isErrorWithCode(error);
|
|
433
|
+
isErrorWithMessage(error);
|
|
434
|
+
isSyntaxError(error);
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
`getSerializationIssues` returns all detected serialization problems with their object paths:
|
|
438
|
+
|
|
439
|
+
```typescript
|
|
440
|
+
const issues = getSerializationIssues({
|
|
441
|
+
user: {
|
|
442
|
+
callback: () => {},
|
|
443
|
+
},
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
/*
|
|
447
|
+
[
|
|
448
|
+
{
|
|
449
|
+
path: '[OBJECT].user.callback',
|
|
450
|
+
kind: 'function',
|
|
451
|
+
message:
|
|
452
|
+
'Field "[OBJECT].user.callback" has non-serializable value of type "function"'
|
|
453
|
+
}
|
|
454
|
+
]
|
|
455
|
+
*/
|
|
88
456
|
```
|