@luvio/adapter-test-library 0.105.0 → 0.108.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.
@@ -1,13 +1,66 @@
1
1
  (function (global, factory) {
2
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('sinon'), require('@luvio/environments')) :
3
- typeof define === 'function' && define.amd ? define(['exports', 'sinon', '@luvio/environments'], factory) :
4
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.luvioAdapterTestLibrary = {}, global.sinon, global.environments));
5
- })(this, (function (exports, sinon, environments) { 'use strict';
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('sinon')) :
3
+ typeof define === 'function' && define.amd ? define(['exports', 'sinon'], factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.luvioAdapterTestLibrary = {}, global.sinon));
5
+ })(this, (function (exports, sinon) { 'use strict';
6
6
 
7
7
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
8
8
 
9
9
  var sinon__default = /*#__PURE__*/_interopDefaultLegacy(sinon);
10
10
 
11
+ /*! *****************************************************************************
12
+ Copyright (c) Microsoft Corporation.
13
+
14
+ Permission to use, copy, modify, and/or distribute this software for any
15
+ purpose with or without fee is hereby granted.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
18
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
19
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
20
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
21
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
22
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
23
+ PERFORMANCE OF THIS SOFTWARE.
24
+ ***************************************************************************** */
25
+
26
+ function __awaiter(thisArg, _arguments, P, generator) {
27
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
28
+ return new (P || (P = Promise))(function (resolve, reject) {
29
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
30
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
31
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
32
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
33
+ });
34
+ }
35
+
36
+ function __generator(thisArg, body) {
37
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
38
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
39
+ function verb(n) { return function (v) { return step([n, v]); }; }
40
+ function step(op) {
41
+ if (f) throw new TypeError("Generator is already executing.");
42
+ while (_) try {
43
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
44
+ if (y = 0, t) op = [op[0] & 2, t.value];
45
+ switch (op[0]) {
46
+ case 0: case 1: t = op; break;
47
+ case 4: _.label++; return { value: op[1], done: false };
48
+ case 5: _.label++; y = op[1]; op = [0]; continue;
49
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
50
+ default:
51
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
52
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
53
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
54
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
55
+ if (t[2]) _.ops.pop();
56
+ _.trys.pop(); continue;
57
+ }
58
+ op = body.call(thisArg, _);
59
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
60
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
61
+ }
62
+ }
63
+
11
64
  /**
12
65
  * Clone an object
13
66
  *
@@ -56,6 +109,9 @@
56
109
  return true;
57
110
  }
58
111
  return false;
112
+ }
113
+ function flushPromises() {
114
+ return new Promise(function (resolve) { return setTimeout(resolve, 0); });
59
115
  }
60
116
 
61
117
  var networkConnectivityStateMap = new WeakMap();
@@ -101,6 +157,31 @@
101
157
  function isOkResponse(status) {
102
158
  return status >= 200 && status <= 299;
103
159
  }
160
+ /**
161
+ * Flushes any pending network requests. Useful for tests that need to ensure all
162
+ * un-awaited background refreshes are complete
163
+ *
164
+ * @param _mockNetworkAdapter {NetworkAdapter} The network adapter instance to flush
165
+ */
166
+ function flushPendingNetworkRequests(_mockNetworkAdapter) {
167
+ return __awaiter(this, void 0, void 0, function () {
168
+ return __generator(this, function (_a) {
169
+ switch (_a.label) {
170
+ case 0:
171
+ // since the network mock is actually synchronous (just returns things wrapped
172
+ // in Promise.resolve/reject) the only thing necessary to flush any pending
173
+ // network activity is to flush the pending microtask queue
174
+ return [4 /*yield*/, flushPromises()];
175
+ case 1:
176
+ // since the network mock is actually synchronous (just returns things wrapped
177
+ // in Promise.resolve/reject) the only thing necessary to flush any pending
178
+ // network activity is to flush the pending microtask queue
179
+ _a.sent();
180
+ return [2 /*return*/];
181
+ }
182
+ });
183
+ });
184
+ }
104
185
  function buildMockNetworkAdapter(mockPayloads) {
105
186
  // any endpoints not setup with a fake will return a rejected promise
106
187
  var networkAdapter = sinon__default["default"].stub().rejects(buildMockSetupError());
@@ -285,59 +366,6 @@
285
366
  return mockFulfilledSnapshot;
286
367
  }
287
368
 
288
- /*! *****************************************************************************
289
- Copyright (c) Microsoft Corporation.
290
-
291
- Permission to use, copy, modify, and/or distribute this software for any
292
- purpose with or without fee is hereby granted.
293
-
294
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
295
- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
296
- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
297
- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
298
- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
299
- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
300
- PERFORMANCE OF THIS SOFTWARE.
301
- ***************************************************************************** */
302
-
303
- function __awaiter(thisArg, _arguments, P, generator) {
304
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
305
- return new (P || (P = Promise))(function (resolve, reject) {
306
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
307
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
308
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
309
- step((generator = generator.apply(thisArg, _arguments || [])).next());
310
- });
311
- }
312
-
313
- function __generator(thisArg, body) {
314
- var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
315
- return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
316
- function verb(n) { return function (v) { return step([n, v]); }; }
317
- function step(op) {
318
- if (f) throw new TypeError("Generator is already executing.");
319
- while (_) try {
320
- if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
321
- if (y = 0, t) op = [op[0] & 2, t.value];
322
- switch (op[0]) {
323
- case 0: case 1: t = op; break;
324
- case 4: _.label++; return { value: op[1], done: false };
325
- case 5: _.label++; y = op[1]; op = [0]; continue;
326
- case 7: op = _.ops.pop(); _.trys.pop(); continue;
327
- default:
328
- if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
329
- if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
330
- if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
331
- if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
332
- if (t[2]) _.ops.pop();
333
- _.trys.pop(); continue;
334
- }
335
- op = body.call(thisArg, _);
336
- } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
337
- if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
338
- }
339
- }
340
-
341
369
  var MemoryDurableStorePersistence = /** @class */ (function () {
342
370
  function MemoryDurableStorePersistence() {
343
371
  this.store = {};
@@ -352,8 +380,18 @@
352
380
  MemoryDurableStorePersistence.prototype.set = function (key, value) {
353
381
  return __awaiter(this, void 0, void 0, function () {
354
382
  return __generator(this, function (_a) {
355
- this.store[key] = value;
356
- return [2 /*return*/];
383
+ switch (_a.label) {
384
+ case 0:
385
+ // simulate a more realistic durable store by making the write wait a
386
+ // tick before actually setting the value
387
+ return [4 /*yield*/, flushPromises()];
388
+ case 1:
389
+ // simulate a more realistic durable store by making the write wait a
390
+ // tick before actually setting the value
391
+ _a.sent();
392
+ this.store[key] = value;
393
+ return [2 /*return*/];
394
+ }
357
395
  });
358
396
  });
359
397
  };
@@ -365,51 +403,95 @@
365
403
  });
366
404
  });
367
405
  };
406
+ MemoryDurableStorePersistence.prototype.flushPendingWork = function () {
407
+ return __awaiter(this, void 0, void 0, function () {
408
+ return __generator(this, function (_a) {
409
+ switch (_a.label) {
410
+ case 0:
411
+ // since this implementation does actual "IO" synchronously the only
412
+ // thing necessary to await any pending IO is to flush the current
413
+ // microtask queue
414
+ return [4 /*yield*/, flushPromises()];
415
+ case 1:
416
+ // since this implementation does actual "IO" synchronously the only
417
+ // thing necessary to await any pending IO is to flush the current
418
+ // microtask queue
419
+ _a.sent();
420
+ return [2 /*return*/];
421
+ }
422
+ });
423
+ });
424
+ };
368
425
  return MemoryDurableStorePersistence;
369
426
  }());
370
427
 
428
+ function waitForPromiseSet(set) {
429
+ // NOTE: we are building an array from the Set at this point in time. If
430
+ // more Promises are added to the Set while this is awaiting it won't
431
+ // await the newly-added Promise. That's what we want.
432
+ return Promise.all(Array.from(set)).then();
433
+ }
371
434
  var MockDurableStore = /** @class */ (function () {
372
435
  function MockDurableStore(persistence) {
373
- // NOTE: This mock class doesn't enforce read/write synchronization
436
+ // for read/write synchronization
437
+ this.writePromises = new Set();
374
438
  this.listeners = new Set();
375
439
  this.persistence = persistence || new MemoryDurableStorePersistence();
376
440
  }
377
441
  MockDurableStore.prototype.getEntries = function (entryIds, segment) {
378
- var returnSource = Object.create(null);
379
- return this.persistence.get(segment).then(function (entries) {
380
- if (entries === undefined) {
381
- return undefined;
382
- }
383
- for (var _i = 0, entryIds_1 = entryIds; _i < entryIds_1.length; _i++) {
384
- var entryId = entryIds_1[_i];
385
- var entry = entries[entryId];
386
- if (entry !== undefined) {
387
- returnSource[entryId] = clone(entry);
442
+ return __awaiter(this, void 0, void 0, function () {
443
+ var entries, returnSource, _i, entryIds_1, entryId, entry;
444
+ return __generator(this, function (_a) {
445
+ switch (_a.label) {
446
+ case 0:
447
+ if (!(this.writePromises.size > 0)) return [3 /*break*/, 2];
448
+ return [4 /*yield*/, waitForPromiseSet(this.writePromises)];
449
+ case 1:
450
+ _a.sent();
451
+ _a.label = 2;
452
+ case 2: return [4 /*yield*/, this.persistence.get(segment)];
453
+ case 3:
454
+ entries = _a.sent();
455
+ if (entries === undefined) {
456
+ return [2 /*return*/, undefined];
457
+ }
458
+ returnSource = Object.create(null);
459
+ for (_i = 0, entryIds_1 = entryIds; _i < entryIds_1.length; _i++) {
460
+ entryId = entryIds_1[_i];
461
+ entry = entries[entryId];
462
+ if (entry !== undefined) {
463
+ returnSource[entryId] = clone(entry);
464
+ }
465
+ }
466
+ return [2 /*return*/, returnSource];
388
467
  }
389
- }
390
- return returnSource;
468
+ });
391
469
  });
392
470
  };
393
471
  MockDurableStore.prototype.getAllEntries = function (segment) {
394
- var returnSource = Object.create(null);
395
- return this.persistence.get(segment).then(function (rawEntries) {
396
- var entries = rawEntries === undefined ? {} : rawEntries;
397
- for (var _i = 0, _a = Object.keys(entries); _i < _a.length; _i++) {
398
- var key = _a[_i];
399
- returnSource[key] = clone(entries[key]);
400
- }
401
- return returnSource;
472
+ return __awaiter(this, void 0, void 0, function () {
473
+ var entries;
474
+ return __generator(this, function (_a) {
475
+ switch (_a.label) {
476
+ case 0:
477
+ if (!(this.writePromises.size > 0)) return [3 /*break*/, 2];
478
+ return [4 /*yield*/, waitForPromiseSet(this.writePromises)];
479
+ case 1:
480
+ _a.sent();
481
+ _a.label = 2;
482
+ case 2: return [4 /*yield*/, this.persistence.get(segment)];
483
+ case 3:
484
+ entries = _a.sent();
485
+ return [2 /*return*/, entries];
486
+ }
487
+ });
402
488
  });
403
489
  };
404
490
  MockDurableStore.prototype.setEntries = function (entries, segment) {
405
- return this.batchOperations([
406
- { entries: entries, segment: segment, type: environments.DurableStoreOperationType.SetEntries },
407
- ]);
491
+ return this.batchOperations([{ entries: entries, segment: segment, type: 'setEntries' }]);
408
492
  };
409
493
  MockDurableStore.prototype.evictEntries = function (ids, segment) {
410
- return this.batchOperations([
411
- { ids: ids, segment: segment, type: environments.DurableStoreOperationType.EvictEntries },
412
- ]);
494
+ return this.batchOperations([{ ids: ids, segment: segment, type: 'evictEntries' }]);
413
495
  };
414
496
  MockDurableStore.prototype.registerOnChangedListener = function (listener) {
415
497
  var _this = this;
@@ -421,24 +503,52 @@
421
503
  };
422
504
  MockDurableStore.prototype.batchOperations = function (operations) {
423
505
  return __awaiter(this, void 0, void 0, function () {
424
- var changes, i, _a, _b;
425
- return __generator(this, function (_c) {
426
- switch (_c.label) {
506
+ var changes, writePromise;
507
+ var _this = this;
508
+ return __generator(this, function (_a) {
509
+ switch (_a.label) {
427
510
  case 0:
428
- changes = [];
429
- i = 0;
430
- _c.label = 1;
511
+ if (!(this.writePromises.size > 0)) return [3 /*break*/, 2];
512
+ return [4 /*yield*/, waitForPromiseSet(this.writePromises)];
431
513
  case 1:
432
- if (!(i < operations.length)) return [3 /*break*/, 4];
433
- _b = (_a = changes).push;
434
- return [4 /*yield*/, this.performOperation(operations[i])];
514
+ _a.sent();
515
+ _a.label = 2;
435
516
  case 2:
436
- _b.apply(_a, [_c.sent()]);
437
- _c.label = 3;
517
+ changes = [];
518
+ writePromise = (function () { return __awaiter(_this, void 0, void 0, function () {
519
+ var _i, operations_1, operation, _a, _b;
520
+ return __generator(this, function (_c) {
521
+ switch (_c.label) {
522
+ case 0:
523
+ _i = 0, operations_1 = operations;
524
+ _c.label = 1;
525
+ case 1:
526
+ if (!(_i < operations_1.length)) return [3 /*break*/, 4];
527
+ operation = operations_1[_i];
528
+ _b = (_a = changes).push;
529
+ return [4 /*yield*/, this.performOperation(operation)];
530
+ case 2:
531
+ _b.apply(_a, [_c.sent()]);
532
+ _c.label = 3;
533
+ case 3:
534
+ _i++;
535
+ return [3 /*break*/, 1];
536
+ case 4: return [2 /*return*/];
537
+ }
538
+ });
539
+ }); })();
540
+ this.writePromises.add(writePromise);
541
+ _a.label = 3;
438
542
  case 3:
439
- i++;
440
- return [3 /*break*/, 1];
543
+ _a.trys.push([3, , 5, 6]);
544
+ return [4 /*yield*/, writePromise];
441
545
  case 4:
546
+ _a.sent();
547
+ return [3 /*break*/, 6];
548
+ case 5:
549
+ this.writePromises.delete(writePromise);
550
+ return [7 /*endfinally*/];
551
+ case 6:
442
552
  this.listeners.forEach(function (listener) {
443
553
  listener(changes);
444
554
  });
@@ -460,13 +570,13 @@
460
570
  entries = rawEntries === undefined ? {} : rawEntries;
461
571
  ids = [];
462
572
  switch (operation.type) {
463
- case environments.DurableStoreOperationType.SetEntries:
573
+ case 'setEntries':
464
574
  ids = Object.keys(operation.entries);
465
575
  ids.forEach(function (id) {
466
576
  entries[id] = clone(operation.entries[id]);
467
577
  });
468
578
  break;
469
- case environments.DurableStoreOperationType.EvictEntries:
579
+ case 'evictEntries':
470
580
  ids = operation.ids;
471
581
  ids.forEach(function (id) {
472
582
  delete entries[id];
@@ -480,6 +590,28 @@
480
590
  });
481
591
  });
482
592
  };
593
+ MockDurableStore.prototype.flushPendingOperations = function () {
594
+ return __awaiter(this, void 0, void 0, function () {
595
+ return __generator(this, function (_a) {
596
+ switch (_a.label) {
597
+ case 0:
598
+ // flush any pending read operations
599
+ return [4 /*yield*/, this.persistence.flushPendingWork()];
600
+ case 1:
601
+ // flush any pending read operations
602
+ _a.sent();
603
+ _a.label = 2;
604
+ case 2:
605
+ if (!(this.writePromises.size > 0)) return [3 /*break*/, 4];
606
+ return [4 /*yield*/, waitForPromiseSet(this.writePromises)];
607
+ case 3:
608
+ _a.sent();
609
+ return [3 /*break*/, 2];
610
+ case 4: return [2 /*return*/];
611
+ }
612
+ });
613
+ });
614
+ };
483
615
  return MockDurableStore;
484
616
  }());
485
617
 
@@ -489,6 +621,7 @@
489
621
  exports.buildFetchResponse = buildFetchResponse;
490
622
  exports.buildMockNetworkAdapter = buildMockNetworkAdapter;
491
623
  exports.buildSuccessMockPayload = buildSuccessMockPayload;
624
+ exports.flushPendingNetworkRequests = flushPendingNetworkRequests;
492
625
  exports.getMockFulfilledSnapshot = getMockFulfilledSnapshot;
493
626
  exports.getMockLuvioWithFulfilledSnapshot = getMockLuvioWithFulfilledSnapshot;
494
627
  exports.getMockNetworkAdapterCallCount = getMockNetworkAdapterCallCount;
@@ -19,3 +19,4 @@ export declare function stripProperties(obj: {
19
19
  [s: string]: any;
20
20
  }, props: string[]): any;
21
21
  export declare function doesThrow(predicate: () => void): boolean;
22
+ export declare function flushPromises(): Promise<unknown>;
package/jest.config.js ADDED
@@ -0,0 +1,16 @@
1
+ const baseConfig = require('../../../scripts/jest/base.config');
2
+
3
+ module.exports = {
4
+ ...baseConfig,
5
+
6
+ displayName: '@luvio/adapter-test-library',
7
+
8
+ roots: ['<rootDir>/src'],
9
+ coverageThreshold: {
10
+ global: {
11
+ branches: 0,
12
+ functions: 0,
13
+ lines: 0,
14
+ },
15
+ },
16
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@luvio/adapter-test-library",
3
- "version": "0.105.0",
3
+ "version": "0.108.0",
4
4
  "description": "Test library for luvio adapters",
5
5
  "repository": {
6
6
  "type": "git",
@@ -18,11 +18,11 @@
18
18
  "watch": "yarn build --watch"
19
19
  },
20
20
  "dependencies": {
21
- "sinon": "^7.5.0"
21
+ "sinon": "^14.0.0"
22
22
  },
23
23
  "devDependencies": {
24
- "@luvio/engine": "0.105.0",
25
- "@luvio/environments": "0.105.0",
24
+ "@luvio/engine": "0.108.0",
25
+ "@luvio/environments": "0.108.0",
26
26
  "@types/sinon": "^7.5.2"
27
27
  },
28
28
  "nx": {
package/rollup.config.js CHANGED
@@ -8,13 +8,16 @@ function rollupConfig(config) {
8
8
  const { formats, target } = config;
9
9
  return {
10
10
  input: entry,
11
+ external: ['sinon'],
11
12
  output: formats.map((format) => {
12
13
  return {
13
14
  file: `${dist}/${format}/${target}/test-library.js`,
14
15
  format,
15
16
  name: 'luvioAdapterTestLibrary',
17
+ globals: { sinon: 'sinon' },
16
18
  };
17
19
  }),
20
+
18
21
  plugins: [
19
22
  typescript({
20
23
  clean: true,
@@ -0,0 +1,142 @@
1
+ import type {
2
+ DurableStore,
3
+ DurableStoreEntries,
4
+ OnDurableStoreChangedListener,
5
+ DurableStoreOperation,
6
+ DurableStoreChange,
7
+ } from '@luvio/environments';
8
+ import { clone } from './utils';
9
+ import type { DurableStorePersistence } from './durableStorePersistence';
10
+ import { MemoryDurableStorePersistence } from './durableStorePersistence';
11
+
12
+ function waitForPromiseSet(set: Set<Promise<unknown>>): Promise<void> {
13
+ // NOTE: we are building an array from the Set at this point in time. If
14
+ // more Promises are added to the Set while this is awaiting it won't
15
+ // await the newly-added Promise. That's what we want.
16
+ return Promise.all(Array.from(set)).then();
17
+ }
18
+
19
+ export class MockDurableStore implements DurableStore {
20
+ // for read/write synchronization
21
+ private writePromises = new Set<Promise<unknown>>();
22
+
23
+ listeners = new Set<OnDurableStoreChangedListener>();
24
+ persistence: DurableStorePersistence;
25
+
26
+ constructor(persistence?: DurableStorePersistence) {
27
+ this.persistence = persistence || new MemoryDurableStorePersistence();
28
+ }
29
+
30
+ async getEntries<T>(
31
+ entryIds: string[],
32
+ segment: string
33
+ ): Promise<DurableStoreEntries<T> | undefined> {
34
+ // await any current write operations
35
+ if (this.writePromises.size > 0) {
36
+ await waitForPromiseSet(this.writePromises);
37
+ }
38
+
39
+ const entries = await this.persistence.get<DurableStoreEntries<T>>(segment);
40
+
41
+ if (entries === undefined) {
42
+ return undefined;
43
+ }
44
+
45
+ const returnSource = Object.create(null);
46
+ for (const entryId of entryIds) {
47
+ const entry = entries[entryId];
48
+ if (entry !== undefined) {
49
+ returnSource[entryId] = clone(entry);
50
+ }
51
+ }
52
+ return returnSource;
53
+ }
54
+
55
+ async getAllEntries<T>(segment: string): Promise<DurableStoreEntries<T> | undefined> {
56
+ // await any current write operations
57
+ if (this.writePromises.size > 0) {
58
+ await waitForPromiseSet(this.writePromises);
59
+ }
60
+
61
+ const entries = await this.persistence.get<DurableStoreEntries<T>>(segment);
62
+
63
+ return entries;
64
+ }
65
+
66
+ setEntries<T>(entries: DurableStoreEntries<T>, segment: string): Promise<void> {
67
+ return this.batchOperations([{ entries, segment, type: 'setEntries' }]);
68
+ }
69
+
70
+ evictEntries(ids: string[], segment: string): Promise<void> {
71
+ return this.batchOperations([{ ids, segment, type: 'evictEntries' }]);
72
+ }
73
+
74
+ registerOnChangedListener(listener: OnDurableStoreChangedListener): () => Promise<void> {
75
+ this.listeners.add(listener);
76
+ return () => {
77
+ this.listeners.delete(listener);
78
+ return Promise.resolve();
79
+ };
80
+ }
81
+
82
+ async batchOperations<T>(operations: DurableStoreOperation<T>[]): Promise<void> {
83
+ // await any current write operations
84
+ if (this.writePromises.size > 0) {
85
+ await waitForPromiseSet(this.writePromises);
86
+ }
87
+
88
+ const changes: DurableStoreChange[] = [];
89
+
90
+ const writePromise = (async () => {
91
+ for (const operation of operations) {
92
+ changes.push(await this.performOperation(operation));
93
+ }
94
+ })();
95
+ this.writePromises.add(writePromise);
96
+
97
+ try {
98
+ await writePromise;
99
+ } finally {
100
+ this.writePromises.delete(writePromise);
101
+ }
102
+
103
+ this.listeners.forEach((listener) => {
104
+ listener(changes);
105
+ });
106
+ }
107
+
108
+ private async performOperation<T>(
109
+ operation: DurableStoreOperation<T>
110
+ ): Promise<DurableStoreChange> {
111
+ const segment = operation.segment;
112
+ const rawEntries = await this.persistence.get<DurableStoreEntries<T>>(segment);
113
+ const entries = rawEntries === undefined ? {} : rawEntries;
114
+ let ids: string[] = [];
115
+ switch (operation.type) {
116
+ case 'setEntries':
117
+ ids = Object.keys(operation.entries);
118
+ ids.forEach((id) => {
119
+ entries[id] = clone(operation.entries[id]);
120
+ });
121
+ break;
122
+ case 'evictEntries':
123
+ ids = operation.ids;
124
+ ids.forEach((id) => {
125
+ delete entries[id];
126
+ });
127
+ }
128
+
129
+ await this.persistence.set(operation.segment, entries);
130
+ return { ids, segment, type: operation.type };
131
+ }
132
+
133
+ async flushPendingOperations(): Promise<void> {
134
+ // flush any pending read operations
135
+ await this.persistence.flushPendingWork();
136
+
137
+ // wait for any pending writes to finish
138
+ while (this.writePromises.size > 0) {
139
+ await waitForPromiseSet(this.writePromises);
140
+ }
141
+ }
142
+ }