UNPKG

@sap-ux/store

Version:

NPM module for storing persistent data

211 lines 7.24 kB
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