@push.rocks/smartconfig 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,222 @@
1
+ import * as plugins from './plugins.js';
2
+ import * as paths from './paths.js';
3
+
4
+ import { Task } from '@push.rocks/taskbuffer';
5
+
6
+ export type TKeyValueStore = 'custom' | 'userHomeDir' | 'ephemeral';
7
+
8
+ export interface IKvStoreConstructorOptions<T> {
9
+ typeArg: TKeyValueStore;
10
+ identityArg: string;
11
+ customPath?: string;
12
+ mandatoryKeys?: Array<keyof T>;
13
+ }
14
+
15
+ /**
16
+ * kvStore is a simple key value store to store data about projects between runs
17
+ */
18
+ export class KeyValueStore<T = any> {
19
+ private dataObject: Partial<T> = {};
20
+ private deletedObject: Partial<T> = {};
21
+ private mandatoryKeys: Set<keyof T> = new Set();
22
+ public changeSubject = new plugins.smartrx.rxjs.Subject<Partial<T>>();
23
+
24
+ private storedStateString: string = '';
25
+ public syncTask = new Task({
26
+ name: 'syncTask',
27
+ buffered: true,
28
+ bufferMax: 1,
29
+ execDelay: 0,
30
+ taskFunction: async () => {
31
+ if (this.type !== 'ephemeral') {
32
+ this.dataObject = {
33
+ ...plugins.smartfile.fs.toObjectSync(this.filePath),
34
+ ...this.dataObject,
35
+ };
36
+ for (const key of Object.keys(this.deletedObject) as Array<keyof T>) {
37
+ delete this.dataObject[key];
38
+ }
39
+ this.deletedObject = {};
40
+ await plugins.smartfile.memory.toFs(
41
+ plugins.smartjson.stringifyPretty(this.dataObject),
42
+ this.filePath,
43
+ );
44
+ }
45
+ const newStateString = plugins.smartjson.stringify(this.dataObject);
46
+
47
+ // change detection
48
+ if (newStateString !== this.storedStateString) {
49
+ this.storedStateString = newStateString;
50
+ this.changeSubject.next(this.dataObject);
51
+ }
52
+ },
53
+ });
54
+
55
+ /**
56
+ * computes the identity and filePath
57
+ */
58
+ private initFilePath = () => {
59
+ if (this.type === 'ephemeral') {
60
+ // No file path is needed for ephemeral type
61
+ return;
62
+ }
63
+ if (this.customPath) {
64
+ // Use custom path if provided
65
+ const absolutePath = plugins.smartpath.transform.makeAbsolute(
66
+ this.customPath,
67
+ paths.cwd,
68
+ );
69
+ this.filePath = absolutePath;
70
+ if (plugins.smartfile.fs.isDirectorySync(this.filePath)) {
71
+ this.filePath = plugins.path.join(
72
+ this.filePath,
73
+ this.identity + '.json',
74
+ );
75
+ }
76
+ plugins.smartfile.fs.ensureFileSync(this.filePath, '{}');
77
+ return;
78
+ }
79
+
80
+ let baseDir: string;
81
+ if (this.type === 'userHomeDir') {
82
+ baseDir = paths.kvUserHomeDirBase;
83
+ } else {
84
+ throw new Error('kv type not supported');
85
+ }
86
+ this.filePath = plugins.path.join(baseDir, this.identity + '.json');
87
+ plugins.smartfile.fs.ensureDirSync(baseDir);
88
+ plugins.smartfile.fs.ensureFileSync(this.filePath, '{}');
89
+ };
90
+
91
+ // if no custom path is provided, try to store at home directory
92
+ public type: TKeyValueStore;
93
+ public identity: string;
94
+ public filePath?: string;
95
+ private customPath?: string; // Optionally allow custom path
96
+
97
+ /**
98
+ * the constructor of keyvalue store
99
+ * @param typeArg
100
+ * @param identityArg
101
+ * @param customPath Optional custom path for the keyValue store
102
+ */
103
+ constructor(optionsArg: IKvStoreConstructorOptions<T>) {
104
+ if (optionsArg.customPath && optionsArg.typeArg !== 'custom') {
105
+ throw new Error('customPath can only be provided if typeArg is custom');
106
+ }
107
+ if (optionsArg.typeArg === 'custom' && !optionsArg.customPath) {
108
+ throw new Error('customPath must be provided if typeArg is custom');
109
+ }
110
+ this.type = optionsArg.typeArg;
111
+ this.identity = optionsArg.identityArg;
112
+ this.customPath = optionsArg.customPath; // Store custom path if provided
113
+ this.initFilePath();
114
+ if (optionsArg.mandatoryKeys) {
115
+ this.setMandatoryKeys(optionsArg.mandatoryKeys);
116
+ }
117
+ }
118
+
119
+ /**
120
+ * reads all keyValue pairs at once and returns them
121
+ */
122
+ public async readAll(): Promise<Partial<T>> {
123
+ await this.syncTask.trigger();
124
+ return this.dataObject;
125
+ }
126
+
127
+ /**
128
+ * reads a keyValueFile from disk
129
+ */
130
+ public async readKey<K extends keyof T>(keyArg: K): Promise<T[K]> {
131
+ await this.syncTask.trigger();
132
+ return this.dataObject[keyArg] as T[K];
133
+ }
134
+
135
+ /**
136
+ * writes a specific key to the keyValueStore
137
+ */
138
+ public async writeKey<K extends keyof T>(
139
+ keyArg: K,
140
+ valueArg: T[K],
141
+ ): Promise<void> {
142
+ await this.writeAll({
143
+ [keyArg]: valueArg,
144
+ } as unknown as Partial<T>);
145
+ }
146
+
147
+ public async deleteKey<K extends keyof T>(keyArg: K): Promise<void> {
148
+ this.deletedObject[keyArg] = this.dataObject[keyArg];
149
+ await this.syncTask.trigger();
150
+ }
151
+
152
+ /**
153
+ * writes all keyValue pairs in the object argument
154
+ */
155
+ public async writeAll(keyValueObject: Partial<T>): Promise<void> {
156
+ this.dataObject = { ...this.dataObject, ...keyValueObject };
157
+ await this.syncTask.trigger();
158
+ }
159
+
160
+ /**
161
+ * wipes a key value store from disk
162
+ */
163
+ public async wipe(): Promise<void> {
164
+ this.dataObject = {};
165
+ if (this.type !== 'ephemeral') {
166
+ await plugins.smartfile.fs.remove(this.filePath);
167
+ }
168
+ }
169
+
170
+ /**
171
+ * resets the KeyValueStore to the initial state by syncing first, deleting all keys, and then triggering a sync again
172
+ */
173
+ public async reset(): Promise<void> {
174
+ await this.syncTask.trigger(); // Sync to get the latest state
175
+
176
+ // Delete all keys from the dataObject and add them to deletedObject
177
+ for (const key of Object.keys(this.dataObject) as Array<keyof T>) {
178
+ this.deletedObject[key] = this.dataObject[key];
179
+ delete this.dataObject[key];
180
+ }
181
+
182
+ await this.syncTask.trigger(); // Sync again to reflect the deletion
183
+ }
184
+
185
+ private setMandatoryKeys(keys: Array<keyof T>) {
186
+ keys.forEach((key) => this.mandatoryKeys.add(key));
187
+ }
188
+
189
+ public async getMissingMandatoryKeys(): Promise<Array<keyof T>> {
190
+ await this.readAll();
191
+ return Array.from(this.mandatoryKeys).filter(
192
+ (key) => !(key in this.dataObject),
193
+ );
194
+ }
195
+
196
+ public async waitForKeysPresent<K extends keyof T>(
197
+ keysArg: K[],
198
+ ): Promise<void> {
199
+ const missingKeys = keysArg.filter((keyArg) => !this.dataObject[keyArg]);
200
+ if (missingKeys.length === 0) {
201
+ return;
202
+ }
203
+ return new Promise<void>((resolve, reject) => {
204
+ const subscription = this.changeSubject.subscribe(() => {
205
+ const missingKeys = keysArg.filter(
206
+ (keyArg) => !this.dataObject[keyArg],
207
+ );
208
+ if (missingKeys.length === 0) {
209
+ subscription.unsubscribe();
210
+ resolve();
211
+ }
212
+ });
213
+ });
214
+ }
215
+
216
+ public async waitForAndGetKey<K extends keyof T>(
217
+ keyArg: K,
218
+ ): Promise<T[K] | undefined> {
219
+ await this.waitForKeysPresent([keyArg]);
220
+ return this.readKey(keyArg);
221
+ }
222
+ }
@@ -0,0 +1,79 @@
1
+ import * as plugins from './plugins.js';
2
+ import * as paths from './paths.js';
3
+
4
+ /**
5
+ * Smartconfig class allows easy configuration of tools
6
+ */
7
+ export class Smartconfig {
8
+ cwd: string;
9
+ lookupPath: string;
10
+ smartconfigJsonExists: boolean;
11
+ smartconfigJsonData: any;
12
+
13
+ /**
14
+ * creates instance of Smartconfig
15
+ */
16
+ constructor(cwdArg?: string) {
17
+ if (cwdArg) {
18
+ this.cwd = cwdArg;
19
+ } else {
20
+ this.cwd = paths.cwd;
21
+ }
22
+ this.checkLookupPath();
23
+ this.checkSmartconfigJsonExists();
24
+ this.checkSmartconfigJsonData();
25
+ }
26
+
27
+ /**
28
+ * merges the supplied options with the ones from smartconfig.json
29
+ */
30
+ dataFor<IToolConfig>(
31
+ toolnameArg: string,
32
+ defaultOptionsArg: any,
33
+ ): IToolConfig {
34
+ let smartconfigToolOptions;
35
+ if (this.smartconfigJsonData[toolnameArg]) {
36
+ smartconfigToolOptions = this.smartconfigJsonData[toolnameArg];
37
+ } else {
38
+ smartconfigToolOptions = {};
39
+ }
40
+ let mergedOptions = {
41
+ ...defaultOptionsArg,
42
+ ...smartconfigToolOptions,
43
+ };
44
+ return mergedOptions;
45
+ }
46
+
47
+ /**
48
+ * checks if the JSON exists
49
+ */
50
+ private checkSmartconfigJsonExists() {
51
+ this.smartconfigJsonExists = plugins.smartfile.fs.fileExistsSync(
52
+ this.lookupPath,
53
+ );
54
+ }
55
+
56
+ /**
57
+ * gets lookupPath
58
+ */
59
+ private checkLookupPath() {
60
+ if (this.cwd) {
61
+ this.lookupPath = plugins.path.join(this.cwd, 'smartconfig.json');
62
+ } else {
63
+ this.lookupPath = paths.configFile;
64
+ }
65
+ }
66
+
67
+ /**
68
+ * get smartconfigJsonData
69
+ */
70
+ private checkSmartconfigJsonData() {
71
+ if (this.smartconfigJsonExists) {
72
+ this.smartconfigJsonData = plugins.smartfile.fs.toObjectSync(
73
+ this.lookupPath,
74
+ );
75
+ } else {
76
+ this.smartconfigJsonData = {};
77
+ }
78
+ }
79
+ }
package/ts/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from './classes.appdata.js';
2
+ export * from './classes.keyvaluestore.js';
3
+ export * from './classes.smartconfig.js';
package/ts/paths.ts ADDED
@@ -0,0 +1,22 @@
1
+ import * as plugins from './plugins.js';
2
+
3
+ // directories
4
+ export let cwd = process.cwd();
5
+ export let packageDir = plugins.path.join(
6
+ plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url),
7
+ '../',
8
+ );
9
+
10
+ // ----------------------
11
+ // keyValueStore specific
12
+ // ----------------------
13
+
14
+ export let home = plugins.smartpath.get.home();
15
+
16
+ /**
17
+ * keyValue base path
18
+ */
19
+ export let kvUserHomeDirBase = plugins.path.join(home, '.smartconfig/kv');
20
+
21
+ // files
22
+ export let configFile = plugins.path.join(cwd, 'smartconfig.json');
package/ts/plugins.ts ADDED
@@ -0,0 +1,25 @@
1
+ import * as tsclass from '@tsclass/tsclass';
2
+
3
+ export { tsclass };
4
+
5
+ import * as qenv from '@push.rocks/qenv';
6
+ import * as smartlog from '@push.rocks/smartlog';
7
+ import * as path from 'path';
8
+ import * as smartfile from '@push.rocks/smartfile';
9
+ import * as smartjson from '@push.rocks/smartjson';
10
+ import * as smartpath from '@push.rocks/smartpath';
11
+ import * as smartpromise from '@push.rocks/smartpromise';
12
+ import * as smartrx from '@push.rocks/smartrx';
13
+ import * as taskbuffer from '@push.rocks/taskbuffer';
14
+
15
+ export {
16
+ qenv,
17
+ smartlog,
18
+ path,
19
+ smartfile,
20
+ smartjson,
21
+ smartpath,
22
+ smartpromise,
23
+ smartrx,
24
+ taskbuffer,
25
+ };