@sap-ux/store
Version:
NPM module for storing persistent data
137 lines • 5.64 kB
JavaScript
import { getHybridStore } from '../data-access/hybrid.js';
import { BackendSystem, BackendSystemKey } from '../entities/backend-system.js';
import { Entities } from './constants.js';
import { ConnectionType } from '../types.js';
import { getBackendSystemType, getSapToolsDirectory, isMatch } from '../utils/index.js';
import { join } from 'node:path';
import { readFileSync, writeFileSync } from 'node:fs';
export const SystemDataProvider = class {
dataAccessor;
entityName = Entities.BackendSystem;
logger;
constructor(logger, options = {}) {
this.logger = logger;
this.dataAccessor = getHybridStore(this.logger, options);
}
async read(key) {
return this.dataAccessor.read({ entityName: this.entityName, id: key.getId() });
}
async write(entity) {
let e;
if (!(entity instanceof BackendSystem)) {
// We need to use the correct class otherwise the annotations are not effective
e = new BackendSystem({ ...entity });
}
else {
e = entity;
}
return this.dataAccessor.write({
entityName: this.entityName,
id: BackendSystemKey.from(entity).getId(),
entity: e
});
}
async delete(entity) {
return this.dataAccessor.del({
entityName: this.entityName,
id: BackendSystemKey.from(entity).getId()
});
}
async getAll(providerRetrievalOptions) {
const migrationRequired = this.isMigrationRequired();
const { includeSensitiveData = true, backendSystemFilter } = providerRetrievalOptions ?? {};
// Always fetch sensitive data if migration is pending
let systems = await this.dataAccessor.readAll({
entityName: this.entityName,
includeSensitiveData: migrationRequired ? true : includeSensitiveData
});
const migrationComplete = await this.migrateBackendSystems(systems, includeSensitiveData);
// If migration occurred, re-read to get the fully updated dataset
if (!migrationComplete) {
systems = await this.dataAccessor.readAll({
entityName: this.entityName,
includeSensitiveData
});
}
return this.applyFilters(Object.values(systems), backendSystemFilter);
}
/**
* Determines whether a migration is required based on a local marker file.
*/
isMigrationRequired() {
const migrationFilePath = join(getSapToolsDirectory(), '.systemsMigrated');
try {
const raw = readFileSync(migrationFilePath, 'utf-8');
const data = JSON.parse(raw);
return !data?.backendSystemMigrationV1;
}
catch {
// If anything fails (file missing, parse error), treat as not migrated
return true;
}
}
/**
* Applies filter objects to a list of backend systems.
*/
applyFilters(systems, filters = {}) {
if (!filters.connectionType) {
// unless a filter for connectionType is explicitly provided, default to filtering by the ABAP Catalog type as this is what existing consumers expect
filters.connectionType = 'abap_catalog';
}
return systems.filter((system) => isMatch(system, filters));
}
/**
* Ensures all stored backend systems are migrated to the latest structure.
*/
async migrateBackendSystems(systems, containsSensitiveData) {
let allMigrated = true;
for (const [id, system] of Object.entries(systems)) {
// Skip already-migrated entries
const alreadyMigrated = system?.hasSensitiveData !== undefined &&
system?.systemType !== undefined &&
system?.connectionType !== undefined;
if (alreadyMigrated) {
continue;
}
allMigrated = false;
const migratedSystem = await this.buildMigratedSystem(system, containsSensitiveData, id);
await this.dataAccessor.partialUpdate({
entityName: this.entityName,
id,
entity: {
hasSensitiveData: migratedSystem.hasSensitiveData,
systemType: migratedSystem.systemType,
connectionType: migratedSystem.connectionType
}
});
}
if (!allMigrated) {
// Write migration marker file
const filePath = join(getSapToolsDirectory(), '.systemsMigrated');
const marker = { backendSystemMigrationV1: new Date().toISOString() };
writeFileSync(filePath, JSON.stringify(marker, null, 2));
}
return allMigrated;
}
/**
* Builds a fully migrated BackendSystem instance.
*/
async buildMigratedSystem(system, containsSensitiveData, id) {
let fullSystem = system;
// Ensure sensitive data available if needed
if (!containsSensitiveData) {
fullSystem = await this.dataAccessor.read({
entityName: this.entityName,
id
});
}
const inferredSystemType = fullSystem.systemType ?? getBackendSystemType(fullSystem) ?? 'OnPrem';
const connectionType = fullSystem?.connectionType ?? ConnectionType.AbapCatalog; // will need to be removed once adding different connection types is possible
return new BackendSystem({
...fullSystem,
systemType: inferredSystemType,
connectionType
});
}
};
//# sourceMappingURL=backend-system.js.map