homebridge-config-ui-x
Version:
A web based management, configuration and control platform for Homebridge.
98 lines • 3.54 kB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { randomBytes } from 'node:crypto';
import { copyFile, open, rename, unlink } from 'node:fs/promises';
import { isAbsolute, normalize } from 'node:path';
import { pid } from 'node:process';
import { Injectable } from '@nestjs/common';
import { readJson } from 'fs-extra/esm';
function assertSafeFsPath(path) {
if (typeof path !== 'string' || path.length === 0) {
throw new TypeError('Path must be a non-empty string');
}
if (!isAbsolute(path) || normalize(path) !== path) {
throw new Error('Refusing fs operation on non-normalised or relative path');
}
}
let JsonFileStoreService = class JsonFileStoreService {
locks = new Map();
async read(path) {
return readJson(path);
}
async mutate(path, mutator, opts = {}) {
return this.withLock(path, async () => {
let current = null;
try {
current = await readJson(path);
}
catch (e) {
if (e?.code !== 'ENOENT') {
throw e;
}
}
const next = await mutator(current);
if (next !== null && next !== undefined) {
await this.atomicWrite(path, next, opts);
}
return next;
});
}
async write(path, data, opts = {}) {
return this.withLock(path, () => this.atomicWrite(path, data, opts));
}
async withLock(path, fn) {
const previous = this.locks.get(path) ?? Promise.resolve();
const guarded = previous.then(fn, fn);
const swallowed = guarded.catch(() => undefined);
this.locks.set(path, swallowed);
try {
return await guarded;
}
finally {
if (this.locks.get(path) === swallowed) {
this.locks.delete(path);
}
}
}
async atomicWrite(path, data, opts) {
assertSafeFsPath(path);
if (opts.backupTo) {
assertSafeFsPath(opts.backupTo);
try {
await copyFile(path, opts.backupTo);
}
catch (e) {
if (e?.code !== 'ENOENT') {
throw e;
}
}
}
const tmpPath = `${path}.tmp.${pid}.${randomBytes(6).toString('hex')}`;
const body = `${JSON.stringify(data, null, opts.spaces ?? 4)}\n`;
let handle;
try {
handle = await open(tmpPath, 'w');
await handle.writeFile(body, 'utf8');
await handle.sync();
}
finally {
await handle?.close();
}
try {
await rename(tmpPath, path);
}
catch (e) {
await unlink(tmpPath).catch(() => undefined);
throw e;
}
}
};
JsonFileStoreService = __decorate([
Injectable()
], JsonFileStoreService);
export { JsonFileStoreService };
//# sourceMappingURL=json-file-store.service.js.map