Persistent user settings for Electron apps with async file reading, built-in JSON schema validation and RAM cached settings
MIT License
Persistent user settings for Electron apps with asynchronous file reading, built-in JSON schema validation and RAM cached settings for immediate accesss to the stored values.
WARNING: This module is on an early development stage, please be cautious and start an issue if you find any bugs (I promise to continue to update this module as fast as I can)
Reading cached setting from memory
Built-in JSON schema validation
Secure and lightweight
Async operation
Centralized settings to prevent conflicts
Written in TypeScript
Well documented and easy to use
npm install --save electron-json-settings-store
or
yarn add electron-json-settings-store
Electron 7.x or later (Node.js v.12.x)
You only need to follow 4 simple steps to start using this module:
const { ElectronJSONSettingsStoreMain } = require('electron-json-settings-store');
// or
import {ElectronJSONSettingsStoreMain, ElectronJSONSettingsStoreMainOptions} from 'electron-json-settings-store';
const schema = {
size: { default: 25, type: 'number', positive: true, integer: true, min: 10, max: 40 },
darkMode: { default: false, type: 'boolean' },
name: { default: 'World', type: 'string' }
};
const config = new ElectronJSONSettingsStoreMain(schema, { watchFile: false, writeBeforeQuit: true });
// sync mode is easy to use, but experience programmers can use async mode
config.initSync();
console.log(config.getAll);
const { ElectronJSONSettingsStoreRenderer } = require('electron-json-settings-store');
const config = new ElectronJSONSettingsStoreRenderer();
window.addEventListener('DOMContentLoaded', () => {
config.init().then(() =>
console.log(config.getAll)
);
});
npm install
to install the dependenciesnpm run test-electron
and browse the sample-app folderProperty | Type | Default | Description |
---|---|---|---|
fileExtension |
string |
json |
Extension of the config file. |
fileName |
string |
config |
Filename without extension. |
filePath |
string |
app.getPath('userData') |
Settings complete filepath. Storage file location. Don't specify this unless absolutely necessary! By default, it will pick the optimal location by adhering to system conventions. |
prettyPrint |
boolean |
true |
Save formatted (pretty print) JSON file. Disable only to save a few bytes/add some performance improvement. |
validateFile |
boolean |
true |
Settings will be validated after file reading. Note: the file is read on startup and on changed content (if watchFile option is true). Prevents the injection of invalid or harmfull config. |
validate |
boolean |
true |
Setting will be validated before is set. Prevents the injection of invalid or harmfull config. |
defaultOnFailValidation |
boolean |
true |
Return default value defined on schema if check validation failed. Recommended to prevent store invalid config. |
watchFile |
boolean |
false |
Watch File for changes. WARNING: Not recommended (feature in test). |
writeBeforeQuit |
boolean |
false |
Save settings before app quits. NOTE: uses sync writing process |
Startup routine (asynchronous file operation).
config.init().then(()=> {
console.log(config.getAll);
} )
// or
await config.init();
Startup routine (synchronous file operation)
config.initSync();
console.log(config.getAll);
Get setting from cache. Return
undefined
if key was not found on cache and schema. WARNING: the JSON file was not read (the value is fectched from cache)
config.get('darkMode');
> false
Returns an object with the current settings
config.getAll;
> {"size": 2, "darkMode": false, "name": "World"}
Returns an object with the default settings defined on schema
config.getDefaults;
> {"size": 25, "darkMode": true, "name": "World"}
Returns the default settings defined on schema. Return undefined if key was not found on schema
config.getDefault('size');
> 25
config.getDefault('SomeInvalidKey');
> undefined
Get complete settings file path
config.getCompleteFilePath;
> c:\users\username\appdata\roaming\app\config.json
Validate key with schema Returns the custom ElectronJSONSettingsStoreResult object.
const schema = {size: { type: 'number', positive: true, integer: true, default: 25, min: 10, max: 40 }}
config.validate('size', 12);
> {status: true, default: 25, errors: false}
config.validate('size', 50);
> {status: false, default: 25, errors: ["The 'size' field must be less than or equal to 40."]}
Sets the given key to cached memory. WARNING: the file is not written. If you also want to write defaults to the file, you need to call writeSync() or write() method after. Returns the custom ElectronJSONSettingsStoreResult object
// set a single key
config.set('debug', true);
// set multiple keys at once
config.set({debug: true, x: 5, y: -9});
// succefull operation ElectronJSONSettingsStoreResult returned object
config.set('size', 15);
> {status: true, default: 25, errors: false}
// if validate option is true
config.set('size', 50);
> {status: false, default: 25, errors: ["The 'size' field must be less than or equal to 40."]}
// if validate option is true and defaultOnFailValidation option is false (applies default)
config.set('size', 50);
> {status: true, default: 25, errors: 'Default setting was applied'}
Sets the given object to cached memory. WARNING: the file is not written. If you also want to write defaults to the file, you need to call writeSync() or write() method after
config.setAll({debug: true, x: 5, y: -9});
Sets the given key to cached memory and write the changes to JSON file (sync file write operation).
config.setAndWriteSync('debug', true);
config.setAndWriteSync({debug: true, x: 5, y: -9});
Sets the given key to cached memory and write the changes to JSON file (async file write operation). Returns the custom ElectronJSONSettingsStoreResult object
await config.setAndWrite('debug', true);
await config.setAndWrite({debug: true, x: 5, y: -9});
Write cached settings to file (sync file write operation). Returns
true
if operation is success or a string with error
config.writeSync(); // success operation
> true
config.writeSync(); // in case of error
> 'Error: ENOENT: no such file...'
Write cached settings to file (async file write operation). Returns
true
if operation is success or a string with error
await config.writeSync(); // success operation
> true
await config.writeSync(); // in case of error
> 'Error: ENOENT: no such file...'
Unsets the given key from the cached config. Returns
true
if operation is success
config.unset('darkMode'); // success operation
> true
Checks if the given key is in the cached config. Returns
true
if the key exists
config.has('darkMode');
> true
config.has('darkmode');
> false
Reset cached settings to default values defined in schema. WARNING: the file is not written. If you also want to write defaults to the file, you need to call
writeSync()
orwrite()
method after.
config.reset();
Reset cached settings to default values defined in schema and write the changes to file (sync file write operation). Returns
true
if operation is success or a string with error
Reset cached settings to default values defined in schema and write the changes to file (async file write operation). Returns
true
if operation is success or a string with error
Unsets the given key from the cached config. Returns
true
if operation success,false
if error or watcher not active
config.disableFileWatcher(); // success operation
> true
Property | Type | Default | Description |
---|---|---|---|
emitEventOnUpdated |
boolean |
false |
Emits event when settings is updated. Disable if you don't need to 'watch' settings change (can lead to a small performance improvment - less event listeners). |
updated
eventIf you enable emitEventOnUpdated
option, an event is emitted when settings are updated. This option is a renderer process exclusive. You can listen to this event by using this code:
config.on('updated', settings => {
console.info('Settings updated! New Settings:');
console.table(settings);
// deal with the new cached settings object
});
Startup routine (async). Recommended method to not block the renderer process
Startup routine (sync). WARNING: Sending a synchronous message will block the whole renderer process until the reply is received, so use this method only as a last resort. It's much better to use the asynchronous version
Get setting from cache. Return
undefined
if key was not found on cache and schema. WARNING: the JSON file was not read (the value is fectched from cache)
config.get('darkMode');
> false
Returns an object with the current settings
config.getAll;
> {"size": 2, "darkMode": false, "name": "World"}
Returns an object with the default settings defined on schema
config.getDefaults;
> {"size": 25, "darkMode": true, "name": "World"}
Returns the default settings defined on schema. Return undefined if key was not found on schema
config.getDefault('size');
> 25
config.getDefault('SomeInvalidKey');
> undefined
Validate key with schema (this is a async function because I don't want to require validation module again on renderer process) Returns the custom ElectronJSONSettingsStoreResult object.
const schema = {size: { type: 'number', positive: true, integer: true, default: 25, min: 10, max: 40 }}
await config.validate('size', 12);
> {status: true, default: 25, errors: false}
await config.validate('size', 50);
> {status: false, default: 25, errors: ["The 'size' field must be less than or equal to 40."]}
Sets the given key to cached memory. WARNING: the file is not written. If you also want to write defaults to the file, you need to call writeSync() or write() method after. Returns the custom ElectronJSONSettingsStoreResult object
// set a single key
await config.set('debug', true);
// set multiple keys at once
await config.set({debug: true, x: 5, y: -9});
// if validate option is true
await config.set('size', 50);
> {status: false, default: 25, errors: ["The 'size' field must be less than or equal to 40."]}
// if validate option is true and defaultOnFailValidation option is false (applies default)
await config.set('size', 50);
> { status: true, default: 25, errors: 'Default setting was applied' }
Sets the given object to cached memory. WARNING: the file is not written. If you also want to write defaults to the file, you need to call writeSync() or write() method after
await config.setAll({debug: true, x: 5, y: -9});
Sets the given key to cached memory and write the changes to JSON file (sync file write operation on the main process, but this method uses an async operation to communicate with the main process, so the result of the function is a promise)
await config.setAndWriteSync('debug', true);
await config.setAndWriteSync({debug: true, x: 5, y: -9});
Sets the given key to cached memory and write the changes to JSON file (async file write operation on the main process). Returns the custom ElectronJSONSettingsStoreResult object
await config.setAndWrite('debug', true);
await config.setAndWrite({debug: true, x: 5, y: -9});
Write cached settings to file (sync file write operation on the main process). Returns
true
if operation is success or a string with error
await config.writeSync(); // success operation
> true
await config.writeSync(); // in case of error
> 'Error: ENOENT: no such file...'
Write cached settings to file (async file write operation on the main process). Returns
true
if operation is success or a string with error
await config.writeSync(); // success operation
> true
await config.writeSync(); // in case of error
> 'Error: ENOENT: no such file...'
Unsets the given key from the cached config. Returns
true
if operation is success
await config.unset('darkMode'); // success operation
> true
Unsets the given key from the cached config. Returns
true
if operation success,false
if error or watcher not active
await config.disableFileWatcher(); // success operation
> true
Checks if the given key is in the cached config. Returns
true
if the key exists
config.has('darkMode');
> true
config.has('darkmode');
> false
Reset cached settings to default values defined in schema. WARNING: the file is not written. If you also want to write defaults to the file, you need to call
writeSync()
orwrite()
method after.
await config.reset();
Reset cached settings to default values defined in schema and write the changes to file (sync file write operation on the main process). Returns
true
if operation is success or a string with error
Reset cached settings to default values defined in schema and write the changes to file (async file write operation on the main process). Returns
true
if operation is success or a string with error
// invalid: no default value
const schema = {darkMode: {type: 'boolean' }};
// valid
const schema = {darkMode: {default: false, type: 'boolean' }};
// other samples
const schema = {
darkMode: {default: false, type: 'boolean' },
email: {default: '[email protected]', type: 'email },
id: { default: 2', type: 'number', positive: true, integer: true },
name: { default: 'john', type: 'string', min: 3, max: 255 },
mac: { default: '01:C8:95:4B:65:FE', type: 'mac' },
uuid: { default: '10ba038e-48da-487b-96e8-8d3b99b6d18a', type: 'uuid' },
url: { default: 'http://google.com', type: 'url' },
dob: { default: new Date(), type: 'date' }
};
documentation)
For more details please check this
ElectronJSONSettingsStoreResult
object // writing operation failed
config.setAndWriteSync('size', 20);
> {status: false, default: 25, default: 'Error ENOENT ...'}
// validation failed
config.validate('size', 50);
> {status: false, default: 25, errors: ["The 'size' field must be less than or equal to 40."]}
// validation passed (option `validate` is enabled)
config.set('size', 22);
> {status: true, default: 25, errors: false}
There are a lot of good store settings libraries out there, I've been using the electron-store module in the last years. But this and other modules have a "big problem" in my opinion: each time you want to access a key-value it reads the JSON file, besides that the reading operation is synchronous so you block the process with a slow IO operation. I wrote a small class extension to fix the "problem" but I wasn't completely satisfied with the result. So I decided to write own my custom library with a different approach. Now the settings file is managed on the main process and the renderer process communicates via IPC with the main process which acts as a "centralized server". It doesn't use the remote module so there is more control over the communication, you can read more about the drawbacks of the remote module on this link . Also by default, you can use asynchronously IO operations so you don't block the running process with a slower disk reading. The settings object is cached on memory so you only need to read from the disk, if the file is changed and setting a new value doesn't oblige you to immediately write the changes on the file (you can write to the JSON file before the app quits or by using the write method). This is my first published module written in TypeScript. I tried to follow the standards and the eslint recommended rules so some lines of code don't look much concize or simplified. This module is on an early stage of development so if you find any bugs or do you have any suggestion please contact me.
Please send pull requests improving the usage and fixing bugs, improving documentation and providing better examples, or providing some tests, because these things are important.
Licensed under MIT
Copyright (c) 2020 [Samuel Carreira]