@sap-ux/store
Version:
NPM module for storing persistent data
150 lines • 6.18 kB
JavaScript
import { SystemDataProvider } from '../data-provider/backend-system.js';
import { BackendSystem, BackendSystemKey } from '../entities/backend-system.js';
import { text } from '../i18n.js';
import { existsSync, writeFileSync, mkdirSync, readFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { getFioriToolsDirectory, getSapToolsDirectory, getEntityFileName } from '../utils/index.js';
import { Entity } from '../constants.js';
/**
* Should not be used directly, use factory method `getService` instead.
* Data integrity cannot be guaranteed when using this class directly.
*/
export class SystemService {
dataProvider;
logger;
constructor(logger, options = {}) {
this.logger = logger;
this.dataProvider = new SystemDataProvider(this.logger, options);
}
async partialUpdate(key, entity) {
this.validatePartialUpdateInput(entity);
const existingSystem = await this.readOrThrow(key);
const updatedEntity = this.mergeProperties(entity, existingSystem);
return this.write(updatedEntity, {
force: true
});
}
mergeProperties(update, existingSystem) {
const patch = { ...update };
// Make sure we don't mess with key fields
delete patch.url;
delete patch.client;
const updatedEntity = { ...existingSystem, ...patch };
return new BackendSystem({ ...updatedEntity });
}
async readOrThrow(key) {
const existingSystem = await this.read(key);
if (!existingSystem) {
throw new Error(text('error.systemDoesNotExist', { system: key }));
}
return existingSystem;
}
validatePartialUpdateInput(entity) {
if (!entity || !Object.keys(entity).length) {
throw new Error(text('error.noPropertiesSpecified'));
}
}
async read(key) {
return this.dataProvider.read(key);
}
/**
* Write the backend system to the store. If a backend entity with the same key already exists and error is thrown.
* Use the `force` option to overwrite, use with cautions and are sure other clients will not break.
*
* @param entity the backend system to write
* @param options
* @param options.force Force overwrite existing backend system with the same key
* @returns
*/
async write(entity, options) {
// Prevent overwrite of existing entity with the same key unless explicitly forced
const entityKey = BackendSystemKey.from(entity);
const existingSystem = await this.read(BackendSystemKey.from(entity));
if (!options?.force && existingSystem) {
throw new Error(text('error.backendSystemEntityKeyExists', { entityKey: entityKey.getId() }));
}
return this.dataProvider.write(entity);
}
async delete(entity) {
return this.dataProvider.delete(entity);
}
/**
* Retrieves all backend systems from the store. Can be filtered by providing retrieval options.
* N.B. if no `connectionType` is provided in the options, only systems with `abap_catalog` connection type will be returned.
*
* @param options - backend service retrieval options
* @returns - list of backend systems
*/
async getAll(options) {
return this.dataProvider.getAll(options);
}
}
export function getInstance(logger, options = {}) {
if (!options.baseDirectory) {
try {
ensureSystemsJsonMigrated();
}
catch (error) {
logger.error(text('error.systemsJsonMigrationFailed', { error: error.message }));
}
options.baseDirectory = getSapToolsDirectory();
}
return new SystemService(logger, options);
}
/**
* Ensure settings are migrated from .fioritools directory to the new .saptools directory.
* If the migration has already taken place, we still check if new systems have been added (possibly via an older version of this module)
* We then migrate only these new systems.
*/
function ensureSystemsJsonMigrated() {
const systemFileName = getEntityFileName(Entity.BackendSystem);
const legacySystemsPath = join(getFioriToolsDirectory(), systemFileName);
const newSystemsPath = join(getSapToolsDirectory(), systemFileName);
const migrationFlag = join(getSapToolsDirectory(), '.systemsMigrated');
if (!existsSync(legacySystemsPath)) {
// No legacy file exists, nothing to migrate
return;
}
const legacyData = JSON.parse(readFileSync(legacySystemsPath, 'utf-8')).systems;
if (existsSync(migrationFlag)) {
migrateNewLegacyPathEntries(newSystemsPath, legacyData);
}
else {
// first time migration, move all data from legacy to new path
mkdirSync(dirname(newSystemsPath), { recursive: true });
writeFileSync(newSystemsPath, JSON.stringify({ systems: legacyData }, null, 2));
writeFileSync(migrationFlag, new Date().toISOString());
}
// overwrite legacy file entries with migrated flag to avoid re-migration
const migratedData = {};
for (const [key, system] of Object.entries(legacyData)) {
migratedData[key] = {
...system,
_migrated: true
};
}
writeFileSync(legacySystemsPath, JSON.stringify({ systems: { ...migratedData } }, null, 2));
}
/**
* Migrates new entries in the systems.json in the legacy path, that have not yet been migrated.
*
* @param newSystemsPath - path to the new systems.json file
* @param legacyData - data from the legacy systems.json file
*/
function migrateNewLegacyPathEntries(newSystemsPath, legacyData) {
let hasNewEntries = false;
const newData = {};
for (const [key, system] of Object.entries(legacyData)) {
if (!system._migrated) {
newData[key] = {
...system
};
hasNewEntries = true;
}
}
if (hasNewEntries) {
const existingData = JSON.parse(readFileSync(newSystemsPath, 'utf-8')).systems;
writeFileSync(newSystemsPath, JSON.stringify({ systems: { ...existingData, ...newData } }, null, 2));
}
}
//# sourceMappingURL=backend-system.js.map