2023-08-03 17:22:34 +00:00
|
|
|
import * as plugins from './npmextra.plugins.js';
|
|
|
|
import * as paths from './npmextra.paths.js';
|
2016-08-28 12:51:04 +00:00
|
|
|
|
2023-08-03 17:22:34 +00:00
|
|
|
import { Task } from '@push.rocks/taskbuffer';
|
2017-07-12 13:30:49 +00:00
|
|
|
|
2023-08-24 08:39:47 +00:00
|
|
|
export type TKeyValueStore = 'custom' | 'userHomeDir';
|
2016-09-24 14:44:48 +00:00
|
|
|
|
2017-07-09 17:05:03 +00:00
|
|
|
/**
|
2023-08-24 08:39:47 +00:00
|
|
|
* kvStore is a simple key value store to store data about projects between runs
|
2017-07-09 17:05:03 +00:00
|
|
|
*/
|
2016-09-24 14:44:48 +00:00
|
|
|
export class KeyValueStore {
|
2019-09-16 11:18:45 +00:00
|
|
|
private dataObject: any = {};
|
|
|
|
private deletedObject: any = {};
|
2024-02-07 17:16:40 +00:00
|
|
|
private mandatoryKeys: Set<string> = new Set();
|
|
|
|
public changeSubject = new plugins.smartrx.rxjs.Subject();
|
|
|
|
|
|
|
|
private storedStateString: string = '';
|
2019-05-10 15:05:04 +00:00
|
|
|
public syncTask = new Task({
|
2019-09-16 11:18:45 +00:00
|
|
|
name: 'syncTask',
|
2017-07-12 15:13:29 +00:00
|
|
|
buffered: true,
|
2021-01-27 21:00:49 +00:00
|
|
|
bufferMax: 1,
|
|
|
|
execDelay: 0,
|
2017-07-12 15:13:29 +00:00
|
|
|
taskFunction: async () => {
|
2024-02-07 17:16:40 +00:00
|
|
|
|
2019-05-10 15:03:07 +00:00
|
|
|
this.dataObject = {
|
|
|
|
...plugins.smartfile.fs.toObjectSync(this.filePath),
|
2021-01-27 21:00:49 +00:00
|
|
|
...this.dataObject,
|
2019-05-10 15:03:07 +00:00
|
|
|
};
|
|
|
|
for (const key of Object.keys(this.deletedObject)) {
|
2018-08-30 23:11:09 +00:00
|
|
|
delete this.dataObject[key];
|
2017-07-12 15:22:22 +00:00
|
|
|
}
|
2018-08-30 23:11:09 +00:00
|
|
|
this.deletedObject = {};
|
2023-08-24 08:44:42 +00:00
|
|
|
await plugins.smartfile.memory.toFs(
|
2023-08-24 10:06:46 +00:00
|
|
|
plugins.smartjson.stringifyPretty(this.dataObject),
|
2023-08-24 08:44:42 +00:00
|
|
|
this.filePath
|
|
|
|
);
|
2024-02-07 17:16:40 +00:00
|
|
|
const newStateString = plugins.smartjson.stringify(this.dataObject);
|
|
|
|
|
|
|
|
// change detection
|
|
|
|
if (newStateString !== this.storedStateString) {
|
|
|
|
this.storedStateString = newStateString;
|
|
|
|
this.changeSubject.next(this.dataObject);
|
|
|
|
}
|
2021-01-27 21:00:49 +00:00
|
|
|
},
|
2018-08-30 23:11:09 +00:00
|
|
|
});
|
2023-08-24 08:44:42 +00:00
|
|
|
|
2019-09-16 11:18:45 +00:00
|
|
|
/**
|
2023-08-24 08:39:47 +00:00
|
|
|
* computes the identity and filePath
|
2019-09-16 11:18:45 +00:00
|
|
|
*/
|
|
|
|
private initFilePath = () => {
|
2023-08-24 08:44:42 +00:00
|
|
|
if (this.customPath) {
|
|
|
|
// Use custom path if provided
|
|
|
|
const absolutePath = plugins.smartpath.transform.makeAbsolute(this.customPath, paths.cwd);
|
2023-08-24 08:39:47 +00:00
|
|
|
this.filePath = absolutePath;
|
|
|
|
if (plugins.smartfile.fs.isDirectorySync(this.filePath)) {
|
|
|
|
this.filePath = plugins.path.join(this.filePath, this.identity + '.json');
|
|
|
|
}
|
|
|
|
plugins.smartfile.fs.ensureFileSync(this.filePath, '{}');
|
|
|
|
return;
|
|
|
|
}
|
2023-08-24 08:44:42 +00:00
|
|
|
|
2019-09-16 11:18:45 +00:00
|
|
|
let baseDir: string;
|
2023-08-24 08:39:47 +00:00
|
|
|
if (this.type === 'userHomeDir') {
|
|
|
|
baseDir = paths.kvUserHomeDirBase;
|
|
|
|
} else {
|
|
|
|
throw new Error('kv type not supported');
|
2019-09-16 11:18:45 +00:00
|
|
|
}
|
|
|
|
this.filePath = plugins.path.join(baseDir, this.identity + '.json');
|
2023-08-24 08:39:47 +00:00
|
|
|
plugins.smartfile.fs.ensureDirSync(baseDir);
|
2019-09-16 11:18:45 +00:00
|
|
|
plugins.smartfile.fs.ensureFileSync(this.filePath, '{}');
|
2019-09-16 11:19:42 +00:00
|
|
|
};
|
2019-09-16 11:18:45 +00:00
|
|
|
|
2023-08-24 08:39:47 +00:00
|
|
|
// if no custom path is provided, try to store at home directory
|
|
|
|
public type: TKeyValueStore;
|
|
|
|
public identity: string;
|
|
|
|
public filePath: string;
|
|
|
|
private customPath?: string; // Optionally allow custom path
|
2017-07-12 15:13:29 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* the constructor of keyvalue store
|
|
|
|
* @param typeArg
|
2023-08-24 08:39:47 +00:00
|
|
|
* @param identityArg
|
|
|
|
* @param customPath Optional custom path for the keyValue store
|
2017-07-12 15:13:29 +00:00
|
|
|
*/
|
2024-02-07 17:16:40 +00:00
|
|
|
constructor(typeArg: TKeyValueStore, identityArg: string, customPath?: string, mandatoryKeys?: string[]) {
|
2023-08-24 08:39:47 +00:00
|
|
|
if (customPath && typeArg !== 'custom') {
|
|
|
|
throw new Error('customPath can only be provided if typeArg is custom');
|
|
|
|
}
|
|
|
|
if (typeArg === 'custom' && !customPath) {
|
|
|
|
throw new Error('customPath must be provided if typeArg is custom');
|
|
|
|
}
|
2018-08-30 23:11:09 +00:00
|
|
|
this.type = typeArg;
|
2023-08-24 08:39:47 +00:00
|
|
|
this.identity = identityArg;
|
|
|
|
this.customPath = customPath; // Store custom path if provided
|
2018-08-30 23:11:09 +00:00
|
|
|
this.initFilePath();
|
2024-02-07 17:16:40 +00:00
|
|
|
if (mandatoryKeys) {
|
|
|
|
this.setMandatoryKeys(mandatoryKeys);
|
|
|
|
}
|
2017-03-18 15:23:47 +00:00
|
|
|
}
|
2016-09-24 19:49:53 +00:00
|
|
|
|
2017-03-18 15:23:47 +00:00
|
|
|
/**
|
2017-07-12 13:30:49 +00:00
|
|
|
* reads all keyValue pairs at once and returns them
|
2017-03-18 15:23:47 +00:00
|
|
|
*/
|
2019-05-10 15:05:04 +00:00
|
|
|
public async readAll() {
|
2019-09-16 11:18:45 +00:00
|
|
|
await this.syncTask.trigger();
|
2018-08-30 23:11:09 +00:00
|
|
|
return this.dataObject;
|
2017-07-12 13:30:49 +00:00
|
|
|
}
|
2016-08-28 12:51:04 +00:00
|
|
|
|
2017-07-12 13:30:49 +00:00
|
|
|
/**
|
|
|
|
* reads a keyValueFile from disk
|
|
|
|
*/
|
2019-05-10 15:05:04 +00:00
|
|
|
public async readKey(keyArg: string) {
|
2019-09-16 11:18:45 +00:00
|
|
|
await this.syncTask.trigger();
|
|
|
|
return this.dataObject[keyArg];
|
2017-03-18 15:23:47 +00:00
|
|
|
}
|
2016-08-28 12:51:04 +00:00
|
|
|
|
2017-03-18 15:23:47 +00:00
|
|
|
/**
|
2017-08-16 16:25:45 +00:00
|
|
|
* writes a specific key to the keyValueStore
|
2017-03-18 15:23:47 +00:00
|
|
|
*/
|
2019-09-16 11:18:45 +00:00
|
|
|
public async writeKey(keyArg: string, valueArg: any) {
|
|
|
|
await this.writeAll({
|
2021-01-27 21:00:49 +00:00
|
|
|
[keyArg]: valueArg,
|
2019-09-16 11:18:45 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
public async deleteKey(keyArg: string) {
|
|
|
|
this.deletedObject[keyArg] = this.dataObject[keyArg];
|
|
|
|
await this.syncTask.trigger();
|
2017-08-16 16:25:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* writes all keyValue pairs in the object argument
|
|
|
|
*/
|
2023-08-24 08:39:47 +00:00
|
|
|
public async writeAll(keyValueObject: { [key: string]: any }) {
|
2019-09-16 11:19:42 +00:00
|
|
|
this.dataObject = { ...this.dataObject, ...keyValueObject };
|
2019-09-16 11:18:45 +00:00
|
|
|
await this.syncTask.trigger();
|
2017-03-18 15:23:47 +00:00
|
|
|
}
|
2016-08-28 12:51:04 +00:00
|
|
|
|
2017-03-18 15:23:47 +00:00
|
|
|
/**
|
|
|
|
* wipes a key value store from disk
|
|
|
|
*/
|
2019-05-10 15:03:07 +00:00
|
|
|
public async wipe() {
|
2019-09-16 11:18:45 +00:00
|
|
|
this.dataObject = {};
|
|
|
|
await plugins.smartfile.fs.remove(this.filePath);
|
2017-03-18 15:23:47 +00:00
|
|
|
}
|
2023-08-24 08:44:42 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* resets the KeyValueStore to the initial state by syncing first, deleting all keys, and then triggering a sync again
|
|
|
|
*/
|
|
|
|
public async reset() {
|
|
|
|
await this.syncTask.trigger(); // Sync to get the latest state
|
|
|
|
|
|
|
|
// Delete all keys from the dataObject and add them to deletedObject
|
|
|
|
for (const key of Object.keys(this.dataObject)) {
|
|
|
|
this.deletedObject[key] = this.dataObject[key];
|
|
|
|
delete this.dataObject[key];
|
|
|
|
}
|
|
|
|
|
|
|
|
await this.syncTask.trigger(); // Sync again to reflect the deletion
|
|
|
|
}
|
2024-02-07 17:16:40 +00:00
|
|
|
|
|
|
|
private setMandatoryKeys(keys: string[]) {
|
|
|
|
keys.forEach(key => this.mandatoryKeys.add(key));
|
|
|
|
}
|
|
|
|
|
|
|
|
public getMissingMandatoryKeys(): string[] {
|
|
|
|
return Array.from(this.mandatoryKeys).filter(key => !(key in this.dataObject));
|
|
|
|
}
|
|
|
|
|
|
|
|
public async waitForKeysPresent(keysArg: []): Promise<void> {
|
|
|
|
const missingKeys = keysArg.filter(keyArg => !this.dataObject[keyArg]);
|
|
|
|
if (missingKeys.length === 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
const subscription = this.changeSubject.subscribe(() => {
|
|
|
|
const missingKeys = keysArg.filter(keyArg => !this.dataObject[keyArg]);
|
|
|
|
if (missingKeys.length === 0) {
|
|
|
|
subscription.unsubscribe();
|
|
|
|
resolve();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2016-09-16 20:28:38 +00:00
|
|
|
}
|