@salesforce/core 5.3.9 → 6.0.1-crdt.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.
- package/lib/config/config.d.ts +4 -4
- package/lib/config/config.js +6 -9
- package/lib/config/configFile.d.ts +4 -3
- package/lib/config/configFile.js +63 -14
- package/lib/config/configStackTypes.d.ts +14 -0
- package/lib/config/configStackTypes.js +3 -0
- package/lib/config/configStore.d.ts +8 -19
- package/lib/config/configStore.js +36 -37
- package/lib/config/lwwMap.d.ts +25 -0
- package/lib/config/lwwMap.js +93 -0
- package/lib/config/lwwRegister.d.ts +19 -0
- package/lib/config/lwwRegister.js +39 -0
- package/lib/config/orgUsersConfig.d.ts +5 -1
- package/lib/config/tokensConfig.d.ts +1 -1
- package/lib/config/ttlConfig.js +4 -1
- package/lib/exported.d.ts +2 -1
- package/lib/org/authInfo.d.ts +3 -1
- package/lib/org/authInfo.js +2 -0
- package/lib/org/org.js +2 -2
- package/lib/org/orgConfigProperties.d.ts +1 -1
- package/lib/org/user.js +1 -2
- package/lib/sfProject.d.ts +3 -3
- package/lib/sfProject.js +5 -19
- package/lib/stateAggregator/accessors/aliasAccessor.d.ts +2 -2
- package/lib/stateAggregator/accessors/aliasAccessor.js +3 -7
- package/lib/stateAggregator/accessors/orgAccessor.d.ts +2 -1
- package/lib/stateAggregator/accessors/orgAccessor.js +1 -0
- package/lib/stateAggregator/accessors/tokenAccessor.d.ts +1 -1
- package/lib/stateAggregator/accessors/tokenAccessor.js +1 -1
- package/lib/testSetup.d.ts +3 -20
- package/lib/testSetup.js +24 -58
- package/lib/util/lockRetryOptions.d.ts +11 -0
- package/lib/util/lockRetryOptions.js +16 -0
- package/lib/util/uniqid.d.ts +14 -0
- package/lib/util/uniqid.js +34 -0
- package/package.json +4 -4
package/lib/config/config.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { JsonPrimitive, Nullable } from '@salesforce/ts-types';
|
|
1
|
+
import { JsonCollection, JsonPrimitive, Nullable } from '@salesforce/ts-types';
|
|
2
2
|
import { OrgConfigProperties } from '../org/orgConfigProperties';
|
|
3
3
|
import { ConfigFile } from './configFile';
|
|
4
|
-
import { ConfigContents, ConfigValue } from './
|
|
4
|
+
import { ConfigContents, ConfigValue } from './configStackTypes';
|
|
5
5
|
/**
|
|
6
6
|
* Interface for meta information about config properties
|
|
7
7
|
*/
|
|
@@ -241,7 +241,7 @@ export declare class Config extends ConfigFile<ConfigFile.Options, ConfigPropert
|
|
|
241
241
|
*
|
|
242
242
|
* @param newContents The new Config value to persist.
|
|
243
243
|
*/
|
|
244
|
-
write(
|
|
244
|
+
write(): Promise<ConfigProperties>;
|
|
245
245
|
/**
|
|
246
246
|
* DO NOT CALL - The config file needs to encrypt values which can only be done asynchronously.
|
|
247
247
|
* Call {@link SfdxConfig.write} instead.
|
|
@@ -260,7 +260,7 @@ export declare class Config extends ConfigFile<ConfigFile.Options, ConfigPropert
|
|
|
260
260
|
* @param key The property to set.
|
|
261
261
|
* @param value The value of the property.
|
|
262
262
|
*/
|
|
263
|
-
set(key: string, value: JsonPrimitive): ConfigProperties;
|
|
263
|
+
set(key: string, value: JsonPrimitive | JsonCollection): ConfigProperties;
|
|
264
264
|
/**
|
|
265
265
|
* Unsets a value for a property.
|
|
266
266
|
*
|
package/lib/config/config.js
CHANGED
|
@@ -281,14 +281,14 @@ class Config extends configFile_1.ConfigFile {
|
|
|
281
281
|
*/
|
|
282
282
|
static async update(isGlobal, propertyName, value) {
|
|
283
283
|
const config = await Config.create({ isGlobal });
|
|
284
|
-
|
|
285
|
-
if (value == null) {
|
|
286
|
-
|
|
284
|
+
await config.read();
|
|
285
|
+
if (value == null || value === undefined) {
|
|
286
|
+
config.unset(propertyName);
|
|
287
287
|
}
|
|
288
288
|
else {
|
|
289
|
-
|
|
289
|
+
config.set(propertyName, value);
|
|
290
290
|
}
|
|
291
|
-
return config.write(
|
|
291
|
+
return config.write();
|
|
292
292
|
}
|
|
293
293
|
/**
|
|
294
294
|
* Clear all the configured properties both local and global.
|
|
@@ -339,10 +339,7 @@ class Config extends configFile_1.ConfigFile {
|
|
|
339
339
|
*
|
|
340
340
|
* @param newContents The new Config value to persist.
|
|
341
341
|
*/
|
|
342
|
-
async write(
|
|
343
|
-
if (newContents != null) {
|
|
344
|
-
this.setContents(newContents);
|
|
345
|
-
}
|
|
342
|
+
async write() {
|
|
346
343
|
await this.cryptProperties(true);
|
|
347
344
|
await super.write();
|
|
348
345
|
if (global_1.Global.SFDX_INTEROPERABILITY)
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
import { Stats as fsStats } from 'fs';
|
|
3
3
|
import { Logger } from '../logger/logger';
|
|
4
|
-
import { BaseConfigStore
|
|
4
|
+
import { BaseConfigStore } from './configStore';
|
|
5
|
+
import { ConfigContents } from './configStackTypes';
|
|
5
6
|
/**
|
|
6
7
|
* Represents a json config file used to manage settings and state. Global config
|
|
7
8
|
* files are stored in the home directory hidden state folder (.sfdx) and local config
|
|
@@ -100,14 +101,14 @@ export declare class ConfigFile<T extends ConfigFile.Options = ConfigFile.Option
|
|
|
100
101
|
*
|
|
101
102
|
* @param newContents The new contents of the file.
|
|
102
103
|
*/
|
|
103
|
-
write(
|
|
104
|
+
write(): Promise<P>;
|
|
104
105
|
/**
|
|
105
106
|
* Write the config file with new contents. If no new contents are provided it will write the existing config
|
|
106
107
|
* contents that were set from {@link ConfigFile.read}, or an empty file if {@link ConfigFile.read} was not called.
|
|
107
108
|
*
|
|
108
109
|
* @param newContents The new contents of the file.
|
|
109
110
|
*/
|
|
110
|
-
writeSync(
|
|
111
|
+
writeSync(): P;
|
|
111
112
|
/**
|
|
112
113
|
* Check to see if the config file exists. Returns `true` if the config file exists and has access, false otherwise.
|
|
113
114
|
*/
|
package/lib/config/configFile.js
CHANGED
|
@@ -11,13 +11,15 @@ const fs = require("fs");
|
|
|
11
11
|
const fs_1 = require("fs");
|
|
12
12
|
const os_1 = require("os");
|
|
13
13
|
const path_1 = require("path");
|
|
14
|
-
const
|
|
14
|
+
const proper_lockfile_1 = require("proper-lockfile");
|
|
15
15
|
const kit_1 = require("@salesforce/kit");
|
|
16
16
|
const global_1 = require("../global");
|
|
17
17
|
const logger_1 = require("../logger/logger");
|
|
18
18
|
const sfError_1 = require("../sfError");
|
|
19
19
|
const internal_1 = require("../util/internal");
|
|
20
|
+
const lockRetryOptions_1 = require("../util/lockRetryOptions");
|
|
20
21
|
const configStore_1 = require("./configStore");
|
|
22
|
+
const lwwMap_1 = require("./lwwMap");
|
|
21
23
|
/**
|
|
22
24
|
* Represents a json config file used to manage settings and state. Global config
|
|
23
25
|
* files are stored in the home directory hidden state folder (.sfdx) and local config
|
|
@@ -146,9 +148,9 @@ class ConfigFile extends configStore_1.BaseConfigStore {
|
|
|
146
148
|
// Only need to read config files once. They are kept up to date
|
|
147
149
|
// internally and updated persistently via write().
|
|
148
150
|
if (!this.hasRead || force) {
|
|
149
|
-
this.logger.info(`Reading config file: ${this.getPath()}`);
|
|
151
|
+
this.logger.info(`Reading config file: ${this.getPath()} because ${!this.hasRead ? 'hasRead is false' : 'force parameter is true'}`);
|
|
150
152
|
const obj = (0, kit_1.parseJsonMap)(await fs.promises.readFile(this.getPath(), 'utf8'), this.getPath());
|
|
151
|
-
this.
|
|
153
|
+
this.setContentsFromFileContents(obj, (await fs.promises.stat(this.getPath(), { bigint: true })).mtimeNs);
|
|
152
154
|
}
|
|
153
155
|
// Necessarily set this even when an error happens to avoid infinite re-reading.
|
|
154
156
|
// To attempt another read, pass `force=true`.
|
|
@@ -159,7 +161,7 @@ class ConfigFile extends configStore_1.BaseConfigStore {
|
|
|
159
161
|
this.hasRead = true;
|
|
160
162
|
if (err.code === 'ENOENT') {
|
|
161
163
|
if (!throwOnNotFound) {
|
|
162
|
-
this.
|
|
164
|
+
this.setContentsFromFileContents({}, process.hrtime.bigint());
|
|
163
165
|
return this.getContents();
|
|
164
166
|
}
|
|
165
167
|
}
|
|
@@ -184,14 +186,18 @@ class ConfigFile extends configStore_1.BaseConfigStore {
|
|
|
184
186
|
if (!this.hasRead || force) {
|
|
185
187
|
this.logger.info(`Reading config file: ${this.getPath()}`);
|
|
186
188
|
const obj = (0, kit_1.parseJsonMap)(fs.readFileSync(this.getPath(), 'utf8'));
|
|
187
|
-
this.
|
|
189
|
+
this.setContentsFromFileContents(obj, fs.statSync(this.getPath(), { bigint: true }).mtimeNs);
|
|
188
190
|
}
|
|
191
|
+
// Necessarily set this even when an error happens to avoid infinite re-reading.
|
|
192
|
+
// To attempt another read, pass `force=true`.
|
|
193
|
+
this.hasRead = true;
|
|
189
194
|
return this.getContents();
|
|
190
195
|
}
|
|
191
196
|
catch (err) {
|
|
197
|
+
this.hasRead = true;
|
|
192
198
|
if (err.code === 'ENOENT') {
|
|
193
199
|
if (!throwOnNotFound) {
|
|
194
|
-
this.
|
|
200
|
+
this.setContentsFromFileContents({}, process.hrtime.bigint());
|
|
195
201
|
return this.getContents();
|
|
196
202
|
}
|
|
197
203
|
}
|
|
@@ -209,18 +215,40 @@ class ConfigFile extends configStore_1.BaseConfigStore {
|
|
|
209
215
|
*
|
|
210
216
|
* @param newContents The new contents of the file.
|
|
211
217
|
*/
|
|
212
|
-
async write(
|
|
213
|
-
|
|
214
|
-
this.setContents(newContents);
|
|
215
|
-
}
|
|
218
|
+
async write() {
|
|
219
|
+
// make sure we can write to the directory
|
|
216
220
|
try {
|
|
217
221
|
await fs.promises.mkdir((0, path_1.dirname)(this.getPath()), { recursive: true });
|
|
218
222
|
}
|
|
219
223
|
catch (err) {
|
|
220
224
|
throw sfError_1.SfError.wrap(err);
|
|
221
225
|
}
|
|
226
|
+
let unlockFn;
|
|
227
|
+
// lock the file. Returns an unlock function to call when done.
|
|
228
|
+
try {
|
|
229
|
+
unlockFn = await (0, proper_lockfile_1.lock)(this.getPath(), lockRetryOptions_1.lockRetryOptions);
|
|
230
|
+
// get the file modstamp. Do this after the lock acquisition in case the file is being written to.
|
|
231
|
+
const fileTimestamp = (await fs.promises.stat(this.getPath(), { bigint: true })).mtimeNs;
|
|
232
|
+
// read the file contents into a LWWMap using the modstamp
|
|
233
|
+
const stateFromFile = (0, lwwMap_1.stateFromContents)((0, kit_1.parseJsonMap)(await fs.promises.readFile(this.getPath(), 'utf8'), this.getPath()), fileTimestamp, this.getPath());
|
|
234
|
+
// merge the new contents into the in-memory LWWMap
|
|
235
|
+
this.contents.merge(stateFromFile);
|
|
236
|
+
}
|
|
237
|
+
catch (err) {
|
|
238
|
+
if (err instanceof Error && err.message.includes('ENOENT')) {
|
|
239
|
+
this.logger.debug(`No file found at ${this.getPath()}. Write will create it.`);
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
throw err;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
// write the merged LWWMap to file
|
|
222
246
|
this.logger.info(`Writing to config file: ${this.getPath()}`);
|
|
223
247
|
await fs.promises.writeFile(this.getPath(), JSON.stringify(this.toObject(), null, 2));
|
|
248
|
+
// unlock the file
|
|
249
|
+
if (typeof unlockFn !== 'undefined') {
|
|
250
|
+
await unlockFn();
|
|
251
|
+
}
|
|
224
252
|
return this.getContents();
|
|
225
253
|
}
|
|
226
254
|
/**
|
|
@@ -229,18 +257,39 @@ class ConfigFile extends configStore_1.BaseConfigStore {
|
|
|
229
257
|
*
|
|
230
258
|
* @param newContents The new contents of the file.
|
|
231
259
|
*/
|
|
232
|
-
writeSync(
|
|
233
|
-
if ((0, ts_types_1.isPlainObject)(newContents)) {
|
|
234
|
-
this.setContents(newContents);
|
|
235
|
-
}
|
|
260
|
+
writeSync() {
|
|
236
261
|
try {
|
|
237
262
|
fs.mkdirSync((0, path_1.dirname)(this.getPath()), { recursive: true });
|
|
238
263
|
}
|
|
239
264
|
catch (err) {
|
|
240
265
|
throw sfError_1.SfError.wrap(err);
|
|
241
266
|
}
|
|
267
|
+
let unlockFn;
|
|
268
|
+
try {
|
|
269
|
+
// lock the file. Returns an unlock function to call when done.
|
|
270
|
+
unlockFn = (0, proper_lockfile_1.lockSync)(this.getPath(), lockRetryOptions_1.lockOptions);
|
|
271
|
+
// get the file modstamp. Do this after the lock acquisition in case the file is being written to.
|
|
272
|
+
const fileTimestamp = fs.statSync(this.getPath(), { bigint: true }).mtimeNs;
|
|
273
|
+
// read the file contents into a LWWMap using the modstamp
|
|
274
|
+
const stateFromFile = (0, lwwMap_1.stateFromContents)((0, kit_1.parseJsonMap)(fs.readFileSync(this.getPath(), 'utf8'), this.getPath()), fileTimestamp, this.getPath());
|
|
275
|
+
// merge the new contents into the in-memory LWWMap
|
|
276
|
+
this.contents.merge(stateFromFile);
|
|
277
|
+
}
|
|
278
|
+
catch (err) {
|
|
279
|
+
if (err instanceof Error && err.message.includes('ENOENT')) {
|
|
280
|
+
this.logger.debug(`No file found at ${this.getPath()}. Write will create it.`);
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
throw err;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
// write the merged LWWMap to file
|
|
242
287
|
this.logger.info(`Writing to config file: ${this.getPath()}`);
|
|
243
288
|
fs.writeFileSync(this.getPath(), JSON.stringify(this.toObject(), null, 2));
|
|
289
|
+
if (typeof unlockFn !== 'undefined') {
|
|
290
|
+
// unlock the file
|
|
291
|
+
unlockFn();
|
|
292
|
+
}
|
|
244
293
|
return this.getContents();
|
|
245
294
|
}
|
|
246
295
|
/**
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Dictionary, AnyJson } from '@salesforce/ts-types';
|
|
2
|
+
export type Key<P extends ConfigContents> = Extract<keyof P, string>;
|
|
3
|
+
/**
|
|
4
|
+
* The allowed types stored in a config store.
|
|
5
|
+
*/
|
|
6
|
+
export type ConfigValue = AnyJson;
|
|
7
|
+
/**
|
|
8
|
+
* The type of entries in a config store defined by the key and value type of {@link ConfigContents}.
|
|
9
|
+
*/
|
|
10
|
+
export type ConfigEntry = [string, ConfigValue];
|
|
11
|
+
/**
|
|
12
|
+
* The type of content a config stores.
|
|
13
|
+
*/
|
|
14
|
+
export type ConfigContents<T = ConfigValue> = Dictionary<T>;
|
|
@@ -1,19 +1,8 @@
|
|
|
1
1
|
import { AsyncOptionalCreatable } from '@salesforce/kit';
|
|
2
|
-
import {
|
|
2
|
+
import { JsonMap, Optional } from '@salesforce/ts-types';
|
|
3
3
|
import { Crypto } from '../crypto/crypto';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
*/
|
|
7
|
-
export type ConfigValue = AnyJson;
|
|
8
|
-
/**
|
|
9
|
-
* The type of entries in a config store defined by the key and value type of {@link ConfigContents}.
|
|
10
|
-
*/
|
|
11
|
-
export type ConfigEntry = [string, ConfigValue];
|
|
12
|
-
/**
|
|
13
|
-
* The type of content a config stores.
|
|
14
|
-
*/
|
|
15
|
-
export type ConfigContents<T = ConfigValue> = Dictionary<T>;
|
|
16
|
-
export type Key<P extends ConfigContents> = Extract<keyof P, string>;
|
|
4
|
+
import { LWWMap } from './lwwMap';
|
|
5
|
+
import { ConfigContents, ConfigEntry, ConfigValue, Key } from './configStackTypes';
|
|
17
6
|
/**
|
|
18
7
|
* An interface for a config object with a persistent store.
|
|
19
8
|
*/
|
|
@@ -47,7 +36,7 @@ export declare abstract class BaseConfigStore<T extends BaseConfigStore.Options
|
|
|
47
36
|
protected static encryptedKeys: Array<string | RegExp>;
|
|
48
37
|
protected options: T;
|
|
49
38
|
protected crypto?: Crypto;
|
|
50
|
-
|
|
39
|
+
protected contents: LWWMap<P>;
|
|
51
40
|
private statics;
|
|
52
41
|
/**
|
|
53
42
|
* Constructor.
|
|
@@ -78,7 +67,6 @@ export declare abstract class BaseConfigStore<T extends BaseConfigStore.Options
|
|
|
78
67
|
/**
|
|
79
68
|
* Returns a boolean asserting whether a value has been associated to the key in the config object or not.
|
|
80
69
|
*
|
|
81
|
-
* @param key The key. Supports query key like `a.b[0]`.
|
|
82
70
|
*/
|
|
83
71
|
has(key: string): boolean;
|
|
84
72
|
/**
|
|
@@ -89,7 +77,7 @@ export declare abstract class BaseConfigStore<T extends BaseConfigStore.Options
|
|
|
89
77
|
* Sets the value for the key in the config object. This will override the existing value.
|
|
90
78
|
* To do a partial update, use {@link BaseConfigStore.update}.
|
|
91
79
|
*
|
|
92
|
-
* @param key The key.
|
|
80
|
+
* @param key The key.
|
|
93
81
|
* @param value The value.
|
|
94
82
|
*/
|
|
95
83
|
set<K extends Key<P>>(key: K, value: P[K]): void;
|
|
@@ -107,7 +95,7 @@ export declare abstract class BaseConfigStore<T extends BaseConfigStore.Options
|
|
|
107
95
|
* Returns `true` if an element in the config object existed and has been removed, or `false` if the element does not
|
|
108
96
|
* exist. {@link BaseConfigStore.has} will return false afterwards.
|
|
109
97
|
*
|
|
110
|
-
* @param key The key
|
|
98
|
+
* @param key The key
|
|
111
99
|
*/
|
|
112
100
|
unset(key: string): boolean;
|
|
113
101
|
/**
|
|
@@ -166,7 +154,8 @@ export declare abstract class BaseConfigStore<T extends BaseConfigStore.Options
|
|
|
166
154
|
*
|
|
167
155
|
* @param obj The object.
|
|
168
156
|
*/
|
|
169
|
-
setContentsFromObject<U extends
|
|
157
|
+
setContentsFromObject<U extends P>(obj: U): void;
|
|
158
|
+
protected setContentsFromFileContents(contents: P, timestamp: bigint): void;
|
|
170
159
|
protected getEncryptedKeys(): Array<string | RegExp>;
|
|
171
160
|
/**
|
|
172
161
|
* This config file has encrypted keys and it should attempt to encrypt them.
|
|
@@ -12,6 +12,7 @@ const ts_types_1 = require("@salesforce/ts-types");
|
|
|
12
12
|
const ts_types_2 = require("@salesforce/ts-types");
|
|
13
13
|
const crypto_1 = require("../crypto/crypto");
|
|
14
14
|
const sfError_1 = require("../sfError");
|
|
15
|
+
const lwwMap_1 = require("./lwwMap");
|
|
15
16
|
/**
|
|
16
17
|
* An abstract class that implements all the config management functions but
|
|
17
18
|
* none of the storage functions.
|
|
@@ -27,6 +28,8 @@ class BaseConfigStore extends kit_1.AsyncOptionalCreatable {
|
|
|
27
28
|
*/
|
|
28
29
|
constructor(options) {
|
|
29
30
|
super(options);
|
|
31
|
+
// Initialized in setContents
|
|
32
|
+
this.contents = new lwwMap_1.LWWMap();
|
|
30
33
|
this.statics = this.constructor;
|
|
31
34
|
this.options = options ?? {};
|
|
32
35
|
this.setContents(this.initialContents());
|
|
@@ -35,11 +38,11 @@ class BaseConfigStore extends kit_1.AsyncOptionalCreatable {
|
|
|
35
38
|
* Returns an array of {@link ConfigEntry} for each element in the config.
|
|
36
39
|
*/
|
|
37
40
|
entries() {
|
|
38
|
-
return (0, ts_types_2.definiteEntriesOf)(this.contents);
|
|
41
|
+
return (0, ts_types_2.definiteEntriesOf)(this.contents.value ?? {});
|
|
39
42
|
}
|
|
40
43
|
get(key, decrypt = false) {
|
|
41
44
|
const k = key;
|
|
42
|
-
let value = this.getMethod(this.contents, k);
|
|
45
|
+
let value = this.getMethod(this.contents.value ?? {}, k);
|
|
43
46
|
if (this.hasEncryption() && decrypt) {
|
|
44
47
|
if ((0, ts_types_2.isJsonMap)(value)) {
|
|
45
48
|
value = this.recursiveDecrypt((0, kit_1.cloneJson)(value), k);
|
|
@@ -63,16 +66,15 @@ class BaseConfigStore extends kit_1.AsyncOptionalCreatable {
|
|
|
63
66
|
/**
|
|
64
67
|
* Returns a boolean asserting whether a value has been associated to the key in the config object or not.
|
|
65
68
|
*
|
|
66
|
-
* @param key The key. Supports query key like `a.b[0]`.
|
|
67
69
|
*/
|
|
68
70
|
has(key) {
|
|
69
|
-
return
|
|
71
|
+
return this.contents.has(key) ?? false;
|
|
70
72
|
}
|
|
71
73
|
/**
|
|
72
74
|
* Returns an array that contains the keys for each element in the config object.
|
|
73
75
|
*/
|
|
74
76
|
keys() {
|
|
75
|
-
return Object.keys(this.contents);
|
|
77
|
+
return Object.keys(this.contents.value ?? {});
|
|
76
78
|
}
|
|
77
79
|
set(key, value) {
|
|
78
80
|
if (this.hasEncryption()) {
|
|
@@ -83,7 +85,14 @@ class BaseConfigStore extends kit_1.AsyncOptionalCreatable {
|
|
|
83
85
|
value = this.encrypt(value);
|
|
84
86
|
}
|
|
85
87
|
}
|
|
86
|
-
|
|
88
|
+
// undefined value means unset
|
|
89
|
+
if (value === undefined) {
|
|
90
|
+
this.unset(key);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
// @ts-expect-error TODO: why isn't key guaranteed to be a string
|
|
94
|
+
this.contents.set(key, value);
|
|
95
|
+
}
|
|
87
96
|
}
|
|
88
97
|
update(key, value) {
|
|
89
98
|
const existingValue = this.get(key, true);
|
|
@@ -96,17 +105,11 @@ class BaseConfigStore extends kit_1.AsyncOptionalCreatable {
|
|
|
96
105
|
* Returns `true` if an element in the config object existed and has been removed, or `false` if the element does not
|
|
97
106
|
* exist. {@link BaseConfigStore.has} will return false afterwards.
|
|
98
107
|
*
|
|
99
|
-
* @param key The key
|
|
108
|
+
* @param key The key
|
|
100
109
|
*/
|
|
101
110
|
unset(key) {
|
|
102
111
|
if (this.has(key)) {
|
|
103
|
-
|
|
104
|
-
delete this.contents[key];
|
|
105
|
-
}
|
|
106
|
-
else {
|
|
107
|
-
// It is a query key, so just set it to undefined
|
|
108
|
-
this.setMethod(this.contents, key, undefined);
|
|
109
|
-
}
|
|
112
|
+
this.contents.delete(key);
|
|
110
113
|
return true;
|
|
111
114
|
}
|
|
112
115
|
return false;
|
|
@@ -118,19 +121,19 @@ class BaseConfigStore extends kit_1.AsyncOptionalCreatable {
|
|
|
118
121
|
* @param keys The keys. Supports query keys like `a.b[0]`.
|
|
119
122
|
*/
|
|
120
123
|
unsetAll(keys) {
|
|
121
|
-
return keys.
|
|
124
|
+
return keys.map((key) => this.unset(key)).every(Boolean);
|
|
122
125
|
}
|
|
123
126
|
/**
|
|
124
127
|
* Removes all key/value pairs from the config object.
|
|
125
128
|
*/
|
|
126
129
|
clear() {
|
|
127
|
-
this.
|
|
130
|
+
this.keys().map((key) => this.unset(key));
|
|
128
131
|
}
|
|
129
132
|
/**
|
|
130
133
|
* Returns an array that contains the values for each element in the config object.
|
|
131
134
|
*/
|
|
132
135
|
values() {
|
|
133
|
-
return (0, ts_types_2.definiteValuesOf)(this.contents);
|
|
136
|
+
return (0, ts_types_2.definiteValuesOf)(this.contents.value ?? {});
|
|
134
137
|
}
|
|
135
138
|
/**
|
|
136
139
|
* Returns the entire config contents.
|
|
@@ -143,13 +146,10 @@ class BaseConfigStore extends kit_1.AsyncOptionalCreatable {
|
|
|
143
146
|
*
|
|
144
147
|
*/
|
|
145
148
|
getContents(decrypt = false) {
|
|
146
|
-
if (!this.contents) {
|
|
147
|
-
this.setContents();
|
|
148
|
-
}
|
|
149
149
|
if (this.hasEncryption() && decrypt) {
|
|
150
|
-
return this.recursiveDecrypt((0, kit_1.cloneJson)(this.contents));
|
|
150
|
+
return this.recursiveDecrypt((0, kit_1.cloneJson)(this.contents?.value ?? {}));
|
|
151
151
|
}
|
|
152
|
-
return this.contents;
|
|
152
|
+
return this.contents?.value ?? {};
|
|
153
153
|
}
|
|
154
154
|
/**
|
|
155
155
|
* Sets the entire config contents.
|
|
@@ -160,7 +160,9 @@ class BaseConfigStore extends kit_1.AsyncOptionalCreatable {
|
|
|
160
160
|
if (this.hasEncryption()) {
|
|
161
161
|
contents = this.recursiveEncrypt(contents);
|
|
162
162
|
}
|
|
163
|
-
|
|
163
|
+
(0, ts_types_1.entriesOf)(contents).map(([key, value]) => {
|
|
164
|
+
this.contents.set(key, value);
|
|
165
|
+
});
|
|
164
166
|
}
|
|
165
167
|
/**
|
|
166
168
|
* Invokes `actionFn` once for each key-value pair present in the config object.
|
|
@@ -168,10 +170,7 @@ class BaseConfigStore extends kit_1.AsyncOptionalCreatable {
|
|
|
168
170
|
* @param {function} actionFn The function `(key: string, value: ConfigValue) => void` to be called for each element.
|
|
169
171
|
*/
|
|
170
172
|
forEach(actionFn) {
|
|
171
|
-
|
|
172
|
-
for (const entry of entries) {
|
|
173
|
-
actionFn(entry[0], entry[1]);
|
|
174
|
-
}
|
|
173
|
+
this.entries().map((entry) => actionFn(entry[0], entry[1]));
|
|
175
174
|
}
|
|
176
175
|
/**
|
|
177
176
|
* Asynchronously invokes `actionFn` once for each key-value pair present in the config object.
|
|
@@ -182,18 +181,14 @@ class BaseConfigStore extends kit_1.AsyncOptionalCreatable {
|
|
|
182
181
|
*/
|
|
183
182
|
async awaitEach(actionFn) {
|
|
184
183
|
const entries = this.entries();
|
|
185
|
-
|
|
186
|
-
// prevent ConfigFile collision bug
|
|
187
|
-
// eslint-disable-next-line no-await-in-loop
|
|
188
|
-
await actionFn(entry[0], entry[1]);
|
|
189
|
-
}
|
|
184
|
+
await Promise.all(entries.map((entry) => actionFn(entry[0], entry[1])));
|
|
190
185
|
}
|
|
191
186
|
/**
|
|
192
187
|
* Convert the config object to a JSON object. Returns the config contents.
|
|
193
188
|
* Same as calling {@link ConfigStore.getContents}
|
|
194
189
|
*/
|
|
195
190
|
toObject() {
|
|
196
|
-
return this.contents;
|
|
191
|
+
return this.contents.value ?? {};
|
|
197
192
|
}
|
|
198
193
|
/**
|
|
199
194
|
* Convert an object to a {@link ConfigContents} and set it as the config contents.
|
|
@@ -201,13 +196,17 @@ class BaseConfigStore extends kit_1.AsyncOptionalCreatable {
|
|
|
201
196
|
* @param obj The object.
|
|
202
197
|
*/
|
|
203
198
|
setContentsFromObject(obj) {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
this.
|
|
199
|
+
const objForWrite = this.hasEncryption() ? this.recursiveEncrypt(obj) : obj;
|
|
200
|
+
(0, ts_types_1.entriesOf)(objForWrite).map(([key, value]) => {
|
|
201
|
+
this.set(key, value);
|
|
207
202
|
});
|
|
208
203
|
}
|
|
204
|
+
setContentsFromFileContents(contents, timestamp) {
|
|
205
|
+
const state = (0, lwwMap_1.stateFromContents)(contents, timestamp, this.contents.id);
|
|
206
|
+
this.contents = new lwwMap_1.LWWMap(this.contents.id, state);
|
|
207
|
+
}
|
|
209
208
|
getEncryptedKeys() {
|
|
210
|
-
return [...(this.options?.encryptedKeys ?? []), ...(this.statics?.encryptedKeys
|
|
209
|
+
return [...(this.options?.encryptedKeys ?? []), ...(this.statics?.encryptedKeys ?? [])];
|
|
211
210
|
}
|
|
212
211
|
/**
|
|
213
212
|
* This config file has encrypted keys and it should attempt to encrypt them.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { LWWRegister } from './lwwRegister';
|
|
2
|
+
import { ConfigContents, Key } from './configStackTypes';
|
|
3
|
+
export declare const SYMBOL_FOR_DELETED: "UNIQUE_IDENTIFIER_FOR_DELETED";
|
|
4
|
+
export type LWWState<P> = {
|
|
5
|
+
[Property in keyof P]: LWWRegister<P[Property] | typeof SYMBOL_FOR_DELETED>['state'];
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* @param contents object aligning with ConfigContents
|
|
9
|
+
* @param timestamp a bigInt that sets the timestamp. Defaults to the current time
|
|
10
|
+
* construct a LWWState from an object
|
|
11
|
+
*
|
|
12
|
+
* */
|
|
13
|
+
export declare const stateFromContents: <P extends ConfigContents<import("@salesforce/ts-types").AnyJson>>(contents: P, timestamp?: bigint, id?: string) => LWWState<P>;
|
|
14
|
+
export declare class LWWMap<P extends ConfigContents> {
|
|
15
|
+
#private;
|
|
16
|
+
readonly id: string;
|
|
17
|
+
constructor(id?: string, state?: LWWState<P>);
|
|
18
|
+
get value(): P;
|
|
19
|
+
get state(): LWWState<P>;
|
|
20
|
+
merge(state: LWWState<P>): LWWState<P>;
|
|
21
|
+
set<K extends Key<P>>(key: K, value: P[K]): void;
|
|
22
|
+
get<K extends Key<P>>(key: K): P[K] | undefined;
|
|
23
|
+
delete(key: string): void;
|
|
24
|
+
has(key: string): boolean;
|
|
25
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2023, salesforce.com, inc.
|
|
4
|
+
* All rights reserved.
|
|
5
|
+
* Licensed under the BSD 3-Clause license.
|
|
6
|
+
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
7
|
+
*/
|
|
8
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
9
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
10
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
11
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
12
|
+
};
|
|
13
|
+
var _LWWMap_data;
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.LWWMap = exports.stateFromContents = exports.SYMBOL_FOR_DELETED = void 0;
|
|
16
|
+
const ts_types_1 = require("@salesforce/ts-types");
|
|
17
|
+
const uniqid_1 = require("../util/uniqid");
|
|
18
|
+
const lwwRegister_1 = require("./lwwRegister");
|
|
19
|
+
exports.SYMBOL_FOR_DELETED = 'UNIQUE_IDENTIFIER_FOR_DELETED';
|
|
20
|
+
/**
|
|
21
|
+
* @param contents object aligning with ConfigContents
|
|
22
|
+
* @param timestamp a bigInt that sets the timestamp. Defaults to the current time
|
|
23
|
+
* construct a LWWState from an object
|
|
24
|
+
*
|
|
25
|
+
* */
|
|
26
|
+
const stateFromContents = (contents, timestamp = process.hrtime.bigint(), id) => Object.fromEntries((0, ts_types_1.entriesOf)(contents).map(([key, value]) => [
|
|
27
|
+
key,
|
|
28
|
+
new lwwRegister_1.LWWRegister(id ?? (0, uniqid_1.uniqid)(), { peer: 'file', timestamp, value }),
|
|
29
|
+
]));
|
|
30
|
+
exports.stateFromContents = stateFromContents;
|
|
31
|
+
class LWWMap {
|
|
32
|
+
constructor(id, state) {
|
|
33
|
+
/** map of key to LWWRegister. Used for managing conflicts */
|
|
34
|
+
_LWWMap_data.set(this, new Map());
|
|
35
|
+
this.id = id ?? (0, uniqid_1.uniqid)();
|
|
36
|
+
// create a new register for each key in the initial state
|
|
37
|
+
for (const [key, register] of (0, ts_types_1.entriesOf)(state ?? {})) {
|
|
38
|
+
__classPrivateFieldGet(this, _LWWMap_data, "f").set(key, new lwwRegister_1.LWWRegister(this.id, register));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
get value() {
|
|
42
|
+
return Object.fromEntries(Array.from(__classPrivateFieldGet(this, _LWWMap_data, "f").entries())
|
|
43
|
+
.filter(([, register]) => register.value !== exports.SYMBOL_FOR_DELETED)
|
|
44
|
+
.map(([key, register]) => [key, register.value]));
|
|
45
|
+
}
|
|
46
|
+
get state() {
|
|
47
|
+
return Object.fromEntries(Array.from(__classPrivateFieldGet(this, _LWWMap_data, "f").entries())
|
|
48
|
+
// .filter(([, register]) => Boolean(register))
|
|
49
|
+
.map(([key, register]) => [key, register.state]));
|
|
50
|
+
}
|
|
51
|
+
merge(state) {
|
|
52
|
+
// recursively merge each key's register with the incoming state for that key
|
|
53
|
+
for (const [key, remote] of (0, ts_types_1.entriesOf)(state)) {
|
|
54
|
+
const local = __classPrivateFieldGet(this, _LWWMap_data, "f").get(key);
|
|
55
|
+
// if the register already exists, merge it with the incoming state
|
|
56
|
+
if (local)
|
|
57
|
+
local.merge(remote);
|
|
58
|
+
// otherwise, instantiate a new `LWWRegister` with the incoming state
|
|
59
|
+
else
|
|
60
|
+
__classPrivateFieldGet(this, _LWWMap_data, "f").set(key, new lwwRegister_1.LWWRegister(this.id, remote));
|
|
61
|
+
}
|
|
62
|
+
return this.state;
|
|
63
|
+
}
|
|
64
|
+
set(key, value) {
|
|
65
|
+
// get the register at the given key
|
|
66
|
+
const register = __classPrivateFieldGet(this, _LWWMap_data, "f").get(key);
|
|
67
|
+
// if the register already exists, set the value
|
|
68
|
+
if (register)
|
|
69
|
+
register.set(value);
|
|
70
|
+
// otherwise, instantiate a new `LWWRegister` with the value
|
|
71
|
+
else
|
|
72
|
+
__classPrivateFieldGet(this, _LWWMap_data, "f").set(key, new lwwRegister_1.LWWRegister(this.id, { peer: this.id, timestamp: process.hrtime.bigint(), value }));
|
|
73
|
+
}
|
|
74
|
+
// TODO: how to handle the deep `get` that is currently allowed ex: get('foo.bar.baz')
|
|
75
|
+
get(key) {
|
|
76
|
+
// map loses the typing
|
|
77
|
+
const value = __classPrivateFieldGet(this, _LWWMap_data, "f").get(key)?.value;
|
|
78
|
+
if (value === exports.SYMBOL_FOR_DELETED)
|
|
79
|
+
return undefined;
|
|
80
|
+
return value;
|
|
81
|
+
}
|
|
82
|
+
delete(key) {
|
|
83
|
+
// set the register to null, if it exists
|
|
84
|
+
__classPrivateFieldGet(this, _LWWMap_data, "f").get(key)?.set(exports.SYMBOL_FOR_DELETED);
|
|
85
|
+
}
|
|
86
|
+
has(key) {
|
|
87
|
+
// if a register doesn't exist or its value is null, the map doesn't contain the key
|
|
88
|
+
return __classPrivateFieldGet(this, _LWWMap_data, "f").has(key) && __classPrivateFieldGet(this, _LWWMap_data, "f").get(key)?.value !== exports.SYMBOL_FOR_DELETED;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
exports.LWWMap = LWWMap;
|
|
92
|
+
_LWWMap_data = new WeakMap();
|
|
93
|
+
//# sourceMappingURL=lwwMap.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
type LWWRegisterState<T> = {
|
|
2
|
+
peer: string;
|
|
3
|
+
timestamp: bigint;
|
|
4
|
+
value: T;
|
|
5
|
+
};
|
|
6
|
+
/** a CRDT implementation. Uses timestamps to resolve conflicts when updating the value (last write wins)
|
|
7
|
+
* mostly based on https://jakelazaroff.com/words/an-interactive-intro-to-crdts/
|
|
8
|
+
*
|
|
9
|
+
* @param T the type of the value stored in the register
|
|
10
|
+
*/
|
|
11
|
+
export declare class LWWRegister<T> {
|
|
12
|
+
readonly id: string;
|
|
13
|
+
state: LWWRegisterState<T>;
|
|
14
|
+
constructor(id: string, state: LWWRegisterState<T>);
|
|
15
|
+
get value(): T;
|
|
16
|
+
set(value: T): void;
|
|
17
|
+
merge(incoming: LWWRegisterState<T>): LWWRegisterState<T>;
|
|
18
|
+
}
|
|
19
|
+
export {};
|