feat(core): safe file write, and also cleanup function using exit-hook package

This commit is contained in:
teidesu 2021-04-24 17:20:01 +03:00
parent 48d328f486
commit 63471115ae

View file

@ -5,14 +5,59 @@ try {
fs = require('fs') fs = require('fs')
} catch (e) {} } catch (e) {}
let exitHook: any = null
try {
exitHook = require('exit-hook')
} catch (e) {}
export class JsonFileStorage extends JsonMemoryStorage { export class JsonFileStorage extends JsonMemoryStorage {
private readonly _filename: string private readonly _filename: string
private readonly _safe: boolean
private readonly _cleanup: boolean
constructor(filename: string) { private readonly _unsubscribe: () => void
constructor(
filename: string,
params?: {
/**
* Whether to save file "safely", meaning that the file will first be saved
* to `${filename}.tmp`, and then renamed to `filename`,
* instead of writing directly to `filename`.
*
* This solves the issue with the storage being saved as
* a blank file because of the app being stopped while
* the storage is being written.
*
* Defaults to `true`
*/
safe?: boolean
/**
* Whether to save file on process exit.
* Uses [`exit-hook`](https://www.npmjs.com/package/exit-hook)
*
* Defaults to `true` if `exit-hook` is installed, otherwise `false`
*/
cleanup?: boolean
}
) {
super() super()
if (!fs) throw new Error('Node fs module is not available!') if (!fs) throw new Error('Node fs module is not available!')
this._filename = filename this._filename = filename
this._safe = params?.safe ?? true
this._cleanup = params?.cleanup ?? !!exitHook
if (this._cleanup && !exitHook) {
throw new Error(
'Cleanup on exit is supported through `exit-hook` library, install it first!'
)
}
if (this._cleanup) {
this._unsubscribe = exitHook(this._onProcessExit.bind(this))
}
} }
async load(): Promise<void> { async load(): Promise<void> {
@ -32,19 +77,45 @@ export class JsonFileStorage extends JsonMemoryStorage {
save(): Promise<void> { save(): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// calling writeFile immediately seems to destroy session when using debugger fs.writeFile(
setTimeout( this._safe ? this._filename + '.tmp' : this._filename,
() => this._saveJson(),
fs.writeFile( (err?: Error) => {
this._filename, if (this._safe) {
this._saveJson(), fs.rename(
(err?: Error) => { this._filename + '.tmp',
if (err) reject(err) this._filename,
else resolve() (err?: Error) => {
} if (err) reject(err)
), else resolve()
0 }
)
} else {
if (err) reject(err)
else resolve()
}
}
) )
}) })
} }
private _onProcessExit(): void {
// on exit handler must be synchronous, thus we use sync methods here
try {
fs.writeFileSync(this._filename, this._saveJson())
} catch (e) {}
if (this._safe) {
try {
fs.unlinkSync(this._filename + '.tmp')
} catch (e) {}
}
}
destroy(): void {
if (this._cleanup) {
this._unsubscribe()
}
}
} }