@lossless.org/client 1.10.0 → 1.11.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.
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@lossless.org/client',
6
- version: '1.10.0',
6
+ version: '1.11.0',
7
7
  description: 'One typed client for NoSQLDB, MongoDB, SQLDB, MariaDB, ClickHouse and S3 object storage.'
8
8
  };
9
9
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSxzQkFBc0I7SUFDNUIsT0FBTyxFQUFFLFFBQVE7SUFDakIsV0FBVyxFQUFFLDBGQUEwRjtDQUN4RyxDQUFBIn0=
@@ -23,32 +23,61 @@ export declare class EasyStore<T> {
23
23
  private easyStorePromise?;
24
24
  private getEasyStore;
25
25
  /**
26
- * reads all keyValue pairs at once and returns them
26
+ * Runs store operations one at a time, in the order they were called. Every
27
+ * read and write of this instance goes through here, so a write builds its
28
+ * next state from the state every earlier write left, and a read never sees
29
+ * the state of a write whose save has not resolved yet. A rejected operation
30
+ * does not stop the ones queued behind it.
27
31
  */
28
- readAll(): Promise<any>;
32
+ private operationQueue;
33
+ private runExclusive;
29
34
  /**
30
- * reads a keyValueFile from disk
35
+ * Persists `nextDataArg` as the store's data. The document's `save()` writes
36
+ * the instance's own fields, so the next state is installed for the save and
37
+ * the previous one is put back when the save rejects: a failed write leaves
38
+ * the cache as it was. Callers hold the operation queue.
39
+ */
40
+ private commit;
41
+ /**
42
+ * reads all keyValue pairs at once and returns them as a copy in the
43
+ * persisted form: mutating the result does not change the store, and later
44
+ * writes do not change the result.
45
+ */
46
+ readAll(): Promise<Partial<T>>;
47
+ /**
48
+ * reads one key and returns a copy of its value in the persisted form, or
49
+ * `undefined` when the key is absent
31
50
  */
32
51
  readKey<TKey extends keyof T>(keyArg: TKey): Promise<Partial<T>[TKey]>;
33
52
  /**
34
- * writes a specific key to the keyValueStore
53
+ * writes a specific key to the keyValueStore. The store keeps a copy, so
54
+ * mutating `valueArg` afterwards does not change the store. When the save
55
+ * rejects, the store keeps its previous state.
35
56
  */
36
57
  writeKey<TKey extends keyof T>(keyArg: TKey, valueArg: T[TKey]): Promise<void>;
58
+ /**
59
+ * deletes a key. When the save rejects, the store keeps its previous state.
60
+ */
37
61
  deleteKey(keyArg: keyof T): Promise<void>;
38
62
  /**
39
63
  * merges all keyValue pairs from the object argument into the store.
40
64
  * Existing keys that are not present in `keyValueObject` are preserved.
41
65
  * To overwrite the entire store and drop missing keys, use `replace()`.
66
+ * The store keeps a copy, so mutating `keyValueObject` afterwards does not
67
+ * change the store. When the save rejects, the store keeps its previous state.
42
68
  */
43
69
  writeAll(keyValueObject: Partial<T>): Promise<void>;
44
70
  /**
45
71
  * atomically replaces the entire store with the given object.
46
72
  * Unlike `writeAll` (which merges), `replace` clears any keys not
47
73
  * present in `keyValueObject`. Useful when you need to drop a key.
74
+ * The store keeps a copy, so mutating `keyValueObject` afterwards does not
75
+ * change the store. When the save rejects, the store keeps its previous state.
48
76
  */
49
77
  replace(keyValueObject: Partial<T>): Promise<void>;
50
78
  /**
51
- * wipes a key value store from disk
79
+ * wipes a key value store from disk. When the save rejects, the store keeps
80
+ * its previous state.
52
81
  */
53
82
  wipe(): Promise<void>;
54
83
  cleanUpEphemeral(): Promise<void>;
@@ -91,6 +91,21 @@ const getEasyStoreClassForDb = (smartdataDbArg) => {
91
91
  easyStoreClassByDb.set(smartdataDbArg, SmartdataEasyStore);
92
92
  return SmartdataEasyStore;
93
93
  };
94
+ /**
95
+ * Copies a store value into the form the store persists: undefined object
96
+ * properties are dropped, an undefined array element becomes null and every
97
+ * value takes its BSON form, exactly as a save and a later read from the
98
+ * database do. The copy shares no object with the argument, so neither side can
99
+ * change the other afterwards. A value BSON cannot encode (a cycle, for
100
+ * example) throws here, before the store's cache is touched.
101
+ */
102
+ const copyInPersistedForm = (valueArg) => {
103
+ if (valueArg === undefined) {
104
+ return valueArg;
105
+ }
106
+ const document = plugins.mongodb.BSON.deserialize(plugins.mongodb.BSON.serialize({ value: valueArg }, { ignoreUndefined: true }));
107
+ return document.value;
108
+ };
94
109
  /**
95
110
  * EasyStore allows the storage of easy objects. It also allows easy sharing of the object between different instances
96
111
  */
@@ -148,59 +163,118 @@ export class EasyStore {
148
163
  return deferred.promise;
149
164
  }
150
165
  /**
151
- * reads all keyValue pairs at once and returns them
166
+ * Runs store operations one at a time, in the order they were called. Every
167
+ * read and write of this instance goes through here, so a write builds its
168
+ * next state from the state every earlier write left, and a read never sees
169
+ * the state of a write whose save has not resolved yet. A rejected operation
170
+ * does not stop the ones queued behind it.
171
+ */
172
+ operationQueue = Promise.resolve();
173
+ runExclusive(operationArg) {
174
+ const result = this.operationQueue.then(operationArg);
175
+ this.operationQueue = result.then(() => undefined, () => undefined);
176
+ return result;
177
+ }
178
+ /**
179
+ * Persists `nextDataArg` as the store's data. The document's `save()` writes
180
+ * the instance's own fields, so the next state is installed for the save and
181
+ * the previous one is put back when the save rejects: a failed write leaves
182
+ * the cache as it was. Callers hold the operation queue.
183
+ */
184
+ async commit(easyStoreArg, nextDataArg) {
185
+ const previousData = easyStoreArg.data;
186
+ easyStoreArg.data = nextDataArg;
187
+ try {
188
+ await easyStoreArg.save();
189
+ }
190
+ catch (error) {
191
+ easyStoreArg.data = previousData;
192
+ throw error;
193
+ }
194
+ }
195
+ /**
196
+ * reads all keyValue pairs at once and returns them as a copy in the
197
+ * persisted form: mutating the result does not change the store, and later
198
+ * writes do not change the result.
152
199
  */
153
200
  async readAll() {
154
- const easyStore = await this.getEasyStore();
155
- return easyStore.data;
201
+ return this.runExclusive(async () => {
202
+ const easyStore = await this.getEasyStore();
203
+ return copyInPersistedForm(easyStore.data);
204
+ });
156
205
  }
157
206
  /**
158
- * reads a keyValueFile from disk
207
+ * reads one key and returns a copy of its value in the persisted form, or
208
+ * `undefined` when the key is absent
159
209
  */
160
210
  async readKey(keyArg) {
161
- const easyStore = await this.getEasyStore();
162
- return easyStore.data[keyArg];
211
+ return this.runExclusive(async () => {
212
+ const easyStore = await this.getEasyStore();
213
+ return copyInPersistedForm(easyStore.data[keyArg]);
214
+ });
163
215
  }
164
216
  /**
165
- * writes a specific key to the keyValueStore
217
+ * writes a specific key to the keyValueStore. The store keeps a copy, so
218
+ * mutating `valueArg` afterwards does not change the store. When the save
219
+ * rejects, the store keeps its previous state.
166
220
  */
167
221
  async writeKey(keyArg, valueArg) {
168
- const easyStore = await this.getEasyStore();
169
- easyStore.data[keyArg] = valueArg;
170
- await easyStore.save();
222
+ const value = copyInPersistedForm(valueArg);
223
+ await this.runExclusive(async () => {
224
+ const easyStore = await this.getEasyStore();
225
+ const nextData = { ...easyStore.data };
226
+ nextData[keyArg] = value;
227
+ await this.commit(easyStore, nextData);
228
+ });
171
229
  }
230
+ /**
231
+ * deletes a key. When the save rejects, the store keeps its previous state.
232
+ */
172
233
  async deleteKey(keyArg) {
173
- const easyStore = await this.getEasyStore();
174
- delete easyStore.data[keyArg];
175
- await easyStore.save();
234
+ await this.runExclusive(async () => {
235
+ const easyStore = await this.getEasyStore();
236
+ const nextData = { ...easyStore.data };
237
+ delete nextData[keyArg];
238
+ await this.commit(easyStore, nextData);
239
+ });
176
240
  }
177
241
  /**
178
242
  * merges all keyValue pairs from the object argument into the store.
179
243
  * Existing keys that are not present in `keyValueObject` are preserved.
180
244
  * To overwrite the entire store and drop missing keys, use `replace()`.
245
+ * The store keeps a copy, so mutating `keyValueObject` afterwards does not
246
+ * change the store. When the save rejects, the store keeps its previous state.
181
247
  */
182
248
  async writeAll(keyValueObject) {
183
- const easyStore = await this.getEasyStore();
184
- easyStore.data = { ...easyStore.data, ...keyValueObject };
185
- await easyStore.save();
249
+ const values = copyInPersistedForm(keyValueObject);
250
+ await this.runExclusive(async () => {
251
+ const easyStore = await this.getEasyStore();
252
+ await this.commit(easyStore, { ...easyStore.data, ...values });
253
+ });
186
254
  }
187
255
  /**
188
256
  * atomically replaces the entire store with the given object.
189
257
  * Unlike `writeAll` (which merges), `replace` clears any keys not
190
258
  * present in `keyValueObject`. Useful when you need to drop a key.
259
+ * The store keeps a copy, so mutating `keyValueObject` afterwards does not
260
+ * change the store. When the save rejects, the store keeps its previous state.
191
261
  */
192
262
  async replace(keyValueObject) {
193
- const easyStore = await this.getEasyStore();
194
- easyStore.data = { ...keyValueObject };
195
- await easyStore.save();
263
+ const values = copyInPersistedForm(keyValueObject);
264
+ await this.runExclusive(async () => {
265
+ const easyStore = await this.getEasyStore();
266
+ await this.commit(easyStore, values);
267
+ });
196
268
  }
197
269
  /**
198
- * wipes a key value store from disk
270
+ * wipes a key value store from disk. When the save rejects, the store keeps
271
+ * its previous state.
199
272
  */
200
273
  async wipe() {
201
- const easyStore = await this.getEasyStore();
202
- easyStore.data = {};
203
- await easyStore.save();
274
+ await this.runExclusive(async () => {
275
+ const easyStore = await this.getEasyStore();
276
+ await this.commit(easyStore, {});
277
+ });
204
278
  }
205
279
  async cleanUpEphemeral() {
206
280
  // Clean up ephemeral data periodically while connected
@@ -211,4 +285,4 @@ export class EasyStore {
211
285
  }
212
286
  }
213
287
  }
214
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5lYXN5c3RvcmUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9ub3NxbGRiL2NsYXNzZXMuZWFzeXN0b3JlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGNBQWMsQ0FBQztBQUN4QyxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFDckQsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBQzlDLE9BQU8sRUFBRSxjQUFjLEVBQUUsSUFBSSxFQUFFLEdBQUcsRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBRTdELE1BQU0sa0JBQWtCLEdBQUcsSUFBSSxPQUFPLEVBQW9CLENBQUM7QUFFM0QsTUFBTSxzQkFBc0IsR0FBRyxDQUFJLGNBQTJCLEVBQUUsRUFBRTtJQUNoRSxNQUFNLGFBQWEsR0FBRyxrQkFBa0IsQ0FBQyxHQUFHLENBQUMsY0FBYyxDQUFDLENBQUM7SUFDN0QsSUFBSSxhQUFhLEVBQUUsQ0FBQztRQUNsQixPQUFPLGFBQWEsQ0FBQztJQUN2QixDQUFDO1FBRUssa0JBQWtCO2dDQUR2QixVQUFVLENBQUMsR0FBRyxFQUFFLENBQUMsY0FBYyxDQUFDOzs7OzBCQUNBLGNBQWM7Ozs7Ozs7Ozs7Ozs7c0NBQXRCLFNBQVEsV0FHaEM7Ozs7c0NBQ0UsR0FBRyxFQUFFO3lDQUdMLElBQUksRUFBRTt3Q0FNTixJQUFJLEVBQUU7b0NBR04sSUFBSSxFQUFFO2dCQVhQLG9LQUFPLE1BQU0sNkJBQU4sTUFBTSx1RkFBVTtnQkFHdkIsNktBQU8sU0FBUyw2QkFBVCxTQUFTLDZGQUdkO2dCQUdGLDBLQUFBLFFBQVEsNkJBQVIsUUFBUSwyRkFBVTtnQkFHbEIsOEpBQU8sSUFBSSw2QkFBSixJQUFJLG1GQUFjO2dCQWpCM0IsNktBa0JDOzs7Z0JBbEJLLHVEQUFrQjs7WUFLZixNQUFN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
288
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5lYXN5c3RvcmUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9ub3NxbGRiL2NsYXNzZXMuZWFzeXN0b3JlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGNBQWMsQ0FBQztBQUN4QyxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFDckQsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBQzlDLE9BQU8sRUFBRSxjQUFjLEVBQUUsSUFBSSxFQUFFLEdBQUcsRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBRTdELE1BQU0sa0JBQWtCLEdBQUcsSUFBSSxPQUFPLEVBQW9CLENBQUM7QUFFM0QsTUFBTSxzQkFBc0IsR0FBRyxDQUFJLGNBQTJCLEVBQUUsRUFBRTtJQUNoRSxNQUFNLGFBQWEsR0FBRyxrQkFBa0IsQ0FBQyxHQUFHLENBQUMsY0FBYyxDQUFDLENBQUM7SUFDN0QsSUFBSSxhQUFhLEVBQUUsQ0FBQztRQUNsQixPQUFPLGFBQWEsQ0FBQztJQUN2QixDQUFDO1FBRUssa0JBQWtCO2dDQUR2QixVQUFVLENBQUMsR0FBRyxFQUFFLENBQUMsY0FBYyxDQUFDOzs7OzBCQUNBLGNBQWM7Ozs7Ozs7Ozs7Ozs7c0NBQXRCLFNBQVEsV0FHaEM7Ozs7c0NBQ0UsR0FBRyxFQUFFO3lDQUdMLElBQUksRUFBRTt3Q0FNTixJQUFJLEVBQUU7b0NBR04sSUFBSSxFQUFFO2dCQVhQLG9LQUFPLE1BQU0sNkJBQU4sTUFBTSx1RkFBVTtnQkFHdkIsNktBQU8sU0FBUyw2QkFBVCxTQUFTLDZGQUdkO2dCQUdGLDBLQUFBLFFBQVEsNkJBQVIsUUFBUSwyRkFBVTtnQkFHbEIsOEpBQU8sSUFBSSw2QkFBSixJQUFJLG1GQUFjO2dCQWpCM0IsNktBa0JDOzs7Z0JBbEJLLHVEQUFrQjs7WUFLZixNQUFN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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lossless.org/client",
3
- "version": "1.10.0",
3
+ "version": "1.11.0",
4
4
  "private": false,
5
5
  "description": "One typed client for NoSQLDB, MongoDB, SQLDB, MariaDB, ClickHouse and S3 object storage.",
6
6
  "exports": {
package/readme.md CHANGED
@@ -102,6 +102,7 @@ Family imports preserve the established SmartData and SmartBucket names and cons
102
102
  | Migration-owned indexes | `toleratedIndexNames` on the model — named undeclared indexes are observed, never created, dropped or verified, and no longer make the topology divergent |
103
103
  | Partial index filters in inspection | `inspectNamespace()`, `inspectCollectionTopology()` and `getIndexInfo()` report a partial index's `partialFilterExpression` exactly as the backend lists it, as a frozen snapshot present only on a partial index |
104
104
  | Key-value store readiness | `EasyStore.ensureInitialized()` — installs the store's declared index at startup, outside any transaction window, so a first `readKey()`/`writeKey()` needs no index DDL |
105
+ | Key-value store copies | `EasyStore` `readKey()`/`readAll()` return copies and `writeKey()`/`writeAll()`/`replace()` store copies, in the persisted BSON form, so a read result is a stable snapshot and a caller-side mutation never reaches the store |
105
106
  | Client deadlines | `timeoutMS` (1..120000) on every ordinary model read and write, instance `save()`/`saveIf()`/`delete()` included, on every exact call, and `session.withTransaction(callback, { timeoutMS })` for a whole transaction, retries and commit included; expiry rejects with `SmartdataPersistenceError` code `timeout`, and an exact write interrupted in flight with `ambiguous_write` |
106
107
  | Cancellation | `signal` on every exact call, `atomicDeleteMany()` and `session.withTransaction(callback, { signal })`, which interrupts the session's operation in flight, rolls the attempt back and never commits after an abort |
107
108
  | Disposable test databases on a unix socket | `SmartdataTestDatabase.create({ connection: { socketPath } })` from `@lossless.org/client/testsupport` reaches an engine that listens only on a unix-domain socket, fault helpers included; the loopback `host`/`port` form is unchanged |
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@lossless.org/client',
6
- version: '1.10.0',
6
+ version: '1.11.0',
7
7
  description: 'One typed client for NoSQLDB, MongoDB, SQLDB, MariaDB, ClickHouse and S3 object storage.'
8
8
  }
@@ -34,6 +34,24 @@ const getEasyStoreClassForDb = <T>(smartdataDbArg: SmartdataDb) => {
34
34
  return SmartdataEasyStore;
35
35
  };
36
36
 
37
+ /**
38
+ * Copies a store value into the form the store persists: undefined object
39
+ * properties are dropped, an undefined array element becomes null and every
40
+ * value takes its BSON form, exactly as a save and a later read from the
41
+ * database do. The copy shares no object with the argument, so neither side can
42
+ * change the other afterwards. A value BSON cannot encode (a cycle, for
43
+ * example) throws here, before the store's cache is touched.
44
+ */
45
+ const copyInPersistedForm = <TValue>(valueArg: TValue): TValue => {
46
+ if (valueArg === undefined) {
47
+ return valueArg;
48
+ }
49
+ const document = plugins.mongodb.BSON.deserialize(
50
+ plugins.mongodb.BSON.serialize({ value: valueArg }, { ignoreUndefined: true }),
51
+ );
52
+ return document.value as TValue;
53
+ };
54
+
37
55
  /**
38
56
  * EasyStore allows the storage of easy objects. It also allows easy sharing of the object between different instances
39
57
  */
@@ -98,65 +116,131 @@ export class EasyStore<T> {
98
116
  }
99
117
 
100
118
  /**
101
- * reads all keyValue pairs at once and returns them
119
+ * Runs store operations one at a time, in the order they were called. Every
120
+ * read and write of this instance goes through here, so a write builds its
121
+ * next state from the state every earlier write left, and a read never sees
122
+ * the state of a write whose save has not resolved yet. A rejected operation
123
+ * does not stop the ones queued behind it.
124
+ */
125
+ private operationQueue: Promise<void> = Promise.resolve();
126
+ private runExclusive<TResult>(operationArg: () => Promise<TResult>): Promise<TResult> {
127
+ const result = this.operationQueue.then(operationArg);
128
+ this.operationQueue = result.then(
129
+ () => undefined,
130
+ () => undefined,
131
+ );
132
+ return result;
133
+ }
134
+
135
+ /**
136
+ * Persists `nextDataArg` as the store's data. The document's `save()` writes
137
+ * the instance's own fields, so the next state is installed for the save and
138
+ * the previous one is put back when the save rejects: a failed write leaves
139
+ * the cache as it was. Callers hold the operation queue.
102
140
  */
103
- public async readAll() {
104
- const easyStore = await this.getEasyStore();
105
- return easyStore.data;
141
+ private async commit(
142
+ easyStoreArg: InstanceType<typeof this.easyStoreClass>,
143
+ nextDataArg: Partial<T>,
144
+ ): Promise<void> {
145
+ const previousData = easyStoreArg.data;
146
+ easyStoreArg.data = nextDataArg;
147
+ try {
148
+ await easyStoreArg.save();
149
+ } catch (error) {
150
+ easyStoreArg.data = previousData;
151
+ throw error;
152
+ }
106
153
  }
107
154
 
108
155
  /**
109
- * reads a keyValueFile from disk
156
+ * reads all keyValue pairs at once and returns them as a copy in the
157
+ * persisted form: mutating the result does not change the store, and later
158
+ * writes do not change the result.
159
+ */
160
+ public async readAll(): Promise<Partial<T>> {
161
+ return this.runExclusive(async () => {
162
+ const easyStore = await this.getEasyStore();
163
+ return copyInPersistedForm(easyStore.data);
164
+ });
165
+ }
166
+
167
+ /**
168
+ * reads one key and returns a copy of its value in the persisted form, or
169
+ * `undefined` when the key is absent
110
170
  */
111
171
  public async readKey<TKey extends keyof T>(keyArg: TKey): Promise<Partial<T>[TKey]> {
112
- const easyStore = await this.getEasyStore();
113
- return easyStore.data[keyArg];
172
+ return this.runExclusive(async () => {
173
+ const easyStore = await this.getEasyStore();
174
+ return copyInPersistedForm(easyStore.data[keyArg]);
175
+ });
114
176
  }
115
177
 
116
178
  /**
117
- * writes a specific key to the keyValueStore
179
+ * writes a specific key to the keyValueStore. The store keeps a copy, so
180
+ * mutating `valueArg` afterwards does not change the store. When the save
181
+ * rejects, the store keeps its previous state.
118
182
  */
119
183
  public async writeKey<TKey extends keyof T>(keyArg: TKey, valueArg: T[TKey]) {
120
- const easyStore = await this.getEasyStore();
121
- easyStore.data[keyArg] = valueArg;
122
- await easyStore.save();
184
+ const value = copyInPersistedForm(valueArg);
185
+ await this.runExclusive(async () => {
186
+ const easyStore = await this.getEasyStore();
187
+ const nextData: Partial<T> = { ...easyStore.data };
188
+ nextData[keyArg] = value;
189
+ await this.commit(easyStore, nextData);
190
+ });
123
191
  }
124
192
 
193
+ /**
194
+ * deletes a key. When the save rejects, the store keeps its previous state.
195
+ */
125
196
  public async deleteKey(keyArg: keyof T) {
126
- const easyStore = await this.getEasyStore();
127
- delete easyStore.data[keyArg];
128
- await easyStore.save();
197
+ await this.runExclusive(async () => {
198
+ const easyStore = await this.getEasyStore();
199
+ const nextData: Partial<T> = { ...easyStore.data };
200
+ delete nextData[keyArg];
201
+ await this.commit(easyStore, nextData);
202
+ });
129
203
  }
130
204
 
131
205
  /**
132
206
  * merges all keyValue pairs from the object argument into the store.
133
207
  * Existing keys that are not present in `keyValueObject` are preserved.
134
208
  * To overwrite the entire store and drop missing keys, use `replace()`.
209
+ * The store keeps a copy, so mutating `keyValueObject` afterwards does not
210
+ * change the store. When the save rejects, the store keeps its previous state.
135
211
  */
136
212
  public async writeAll(keyValueObject: Partial<T>) {
137
- const easyStore = await this.getEasyStore();
138
- easyStore.data = { ...easyStore.data, ...keyValueObject };
139
- await easyStore.save();
213
+ const values = copyInPersistedForm(keyValueObject);
214
+ await this.runExclusive(async () => {
215
+ const easyStore = await this.getEasyStore();
216
+ await this.commit(easyStore, { ...easyStore.data, ...values });
217
+ });
140
218
  }
141
219
 
142
220
  /**
143
221
  * atomically replaces the entire store with the given object.
144
222
  * Unlike `writeAll` (which merges), `replace` clears any keys not
145
223
  * present in `keyValueObject`. Useful when you need to drop a key.
224
+ * The store keeps a copy, so mutating `keyValueObject` afterwards does not
225
+ * change the store. When the save rejects, the store keeps its previous state.
146
226
  */
147
227
  public async replace(keyValueObject: Partial<T>) {
148
- const easyStore = await this.getEasyStore();
149
- easyStore.data = { ...keyValueObject };
150
- await easyStore.save();
228
+ const values = copyInPersistedForm(keyValueObject);
229
+ await this.runExclusive(async () => {
230
+ const easyStore = await this.getEasyStore();
231
+ await this.commit(easyStore, values);
232
+ });
151
233
  }
152
234
 
153
235
  /**
154
- * wipes a key value store from disk
236
+ * wipes a key value store from disk. When the save rejects, the store keeps
237
+ * its previous state.
155
238
  */
156
239
  public async wipe() {
157
- const easyStore = await this.getEasyStore();
158
- easyStore.data = {};
159
- await easyStore.save();
240
+ await this.runExclusive(async () => {
241
+ const easyStore = await this.getEasyStore();
242
+ await this.commit(easyStore, {});
243
+ });
160
244
  }
161
245
 
162
246
  public async cleanUpEphemeral() {