@sap-ux/store
Version:
NPM module for storing persistent data
211 lines • 7.24 kB
JavaScript
import path from 'node:path';
import fs, { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
import { errorInstance, getEntityFileName, getFioriToolsDirectory, toPersistenceName } from '../utils/index.js';
import os from 'node:os';
export const basedir = ({ baseDirectory } = {}) => {
if (!baseDirectory) {
return getFioriToolsDirectory();
}
else if (path.isAbsolute(baseDirectory)) {
return baseDirectory;
}
else {
return path.join(os.homedir(), baseDirectory);
}
};
class FilesystemStore {
logger;
storeDirectory;
constructor(logger, options = {}) {
this.logger = logger;
this.storeDirectory = basedir(options);
}
async read({ entityName, id }) {
const name = toPersistenceName(entityName);
if (!name) {
this.logger.debug('read: Entity Type is falsy - ' + entityName);
return undefined;
}
const { entities, error } = this._readAll(name);
if (error) {
if (error.code === 'ENOENT') {
return undefined;
}
else {
throw error;
}
}
if (!entities) {
this.logger.debug(`read: After parsing, entities is falsy. Entity: ${name}, parsed value: ${entities}`);
return undefined;
}
return entities[id];
}
async getAll({ entityName }) {
const name = toPersistenceName(entityName);
if (!name) {
this.logger.debug('read: Entity Type is falsy - ' + entityName);
return [];
}
const { entities, error } = this._readAll(name);
if (error) {
if (error.code === 'ENOENT') {
return [];
}
else {
throw error;
}
}
if (!entities) {
this.logger.debug(`read: After parsing, entities is falsy. Entity: ${name}, parsed value: ${entities}`);
return [];
}
return Object.values(entities);
}
async readAll({ entityName }) {
const name = toPersistenceName(entityName);
if (!name) {
this.logger.debug('read: Entity Type is falsy - ' + entityName);
return {};
}
const { entities, error } = this._readAll(name);
if (error) {
if (error.code === 'ENOENT') {
return {};
}
else {
throw error;
}
}
if (!entities) {
this.logger.debug(`read: After parsing, entities is falsy. Entity: ${name}, parsed value: ${entities}`);
return {};
}
return entities;
}
async write({ entityName, id, entity }) {
const name = toPersistenceName(entityName);
if (!name) {
this.logger.debug('write: Entity is falsy - ' + name);
return undefined;
}
const { entities = {}, error } = this._readAll(name);
if (error && error.code !== 'ENOENT') {
throw error;
}
entities[id] = entity;
this.writeToFile(name, entities);
return entity;
}
async partialUpdate({ entityName, id, entity }) {
if (!entity || !Object.keys(entity).length) {
this.logger.debug('partialUpdate: No properties specified for update');
return undefined;
}
const existingSystem = await this.read({ entityName, id });
if (!existingSystem) {
this.logger.debug(`partialUpdate: Entity with id ${id} does not exist in ${entityName}. Cannot update.`);
return undefined;
}
const updatedEntity = this.mergeProperties(entity, existingSystem);
return this.write({ entityName, id, entity: updatedEntity });
}
mergeProperties(update, existingSystem) {
const updatedEntity = { ...existingSystem, ...update };
return { ...updatedEntity };
}
async del({ entityName, id }) {
const name = toPersistenceName(entityName);
if (!name) {
this.logger.debug('delete: Entity is falsy - ' + name);
return false;
}
const { entities = {}, error } = this._readAll(name);
if (error) {
if (error.code !== 'ENOENT') {
throw error;
}
}
const exists = !!entities[id];
if (exists) {
this.logger.debug(`delete: entity found for id - ${id}. Deleting`);
delete entities[id];
this.writeToFile(name, entities);
return true;
}
else {
this.logger.debug('delete: entity not found');
return false;
}
}
_readAll(entityName) {
let rawContents;
try {
const configPath = path.join(this.storeDirectory, `${entityName}.json`);
if (existsSync(configPath)) {
rawContents = readFileSync(configPath).toString().trim();
}
else {
rawContents = '';
}
}
catch (e) {
const err = errorInstance(e);
this.logger.debug(err.message);
return { error: err };
}
if (!rawContents) {
return { entities: undefined };
}
let entities;
try {
entities = JSON.parse(rawContents)?.[entityName];
}
catch (e) {
return { error: errorInstance(e) };
}
return { entities };
}
writeToFile(entityName, entities) {
const data = JSON.stringify({ [entityName]: entities }, null, 2);
const filename = getEntityFileName(entityName);
try {
writeFileSync(path.join(this.storeDirectory, filename), data);
}
catch (e) {
const err = errorInstance(e);
if (err?.code === 'ENOENT') {
this.logger.debug(`Base directory [${this.storeDirectory}] does not exist, trying to create it`);
mkdirSync(this.storeDirectory, { recursive: true });
writeFileSync(path.join(this.storeDirectory, filename), data);
}
else {
throw e;
}
}
}
}
/** Return an FSWatcher for a given entity name
* The client is responsible for disposing of the FSWatcher
*/
export function getFilesystemWatcherFor(entityName, callback, options = {}) {
const watchPath = path.join(basedir(options), getEntityFileName(entityName));
if (existsSync(watchPath)) {
return fs.watch(watchPath, undefined, () => {
callback(entityName);
});
}
else {
console.warn(`File Not Found: ${watchPath}`);
return undefined;
}
}
/**
* Filesystem store. The entity is stored in JSON format (don't depend on the format, this could change).
* The entity is stored in a file named with the plural form of the entity name in the base directory. Again, this is an
* implementation detail, please don't depend on it.
*/
export function getFilesystemStore(logger, options) {
return new FilesystemStore(logger, options);
}
//# sourceMappingURL=filesystem.js.map