mongodb 3.5.3 → 3.5.7

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/HISTORY.md CHANGED
@@ -2,6 +2,66 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ <a name="3.5.7"></a>
6
+ ## [3.5.7](https://github.com/mongodb/node-mongodb-native/compare/v3.5.6...v3.5.7) (2020-04-29)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * limit growth of server sessions through lazy acquisition ([3d05a6d](https://github.com/mongodb/node-mongodb-native/commit/3d05a6d))
12
+ * remove circular dependency warnings on node 14 ([56a1b8a](https://github.com/mongodb/node-mongodb-native/commit/56a1b8a))
13
+
14
+
15
+
16
+ <a name="3.5.6"></a>
17
+ ## [3.5.6](https://github.com/mongodb/node-mongodb-native/compare/v3.5.5...v3.5.6) (2020-04-14)
18
+
19
+
20
+ ### Bug Fixes
21
+
22
+ * always return empty array for selection on unknown topology ([f9e786a](https://github.com/mongodb/node-mongodb-native/commit/f9e786a))
23
+ * createCollection only uses listCollections in strict mode ([d368f12](https://github.com/mongodb/node-mongodb-native/commit/d368f12))
24
+ * don't throw if `withTransaction()` callback rejects with a null reason ([153646c](https://github.com/mongodb/node-mongodb-native/commit/153646c))
25
+ * only mark server session dirty if the client session is alive ([611be8d](https://github.com/mongodb/node-mongodb-native/commit/611be8d))
26
+ * polyfill for util.promisify ([1c4cf6c](https://github.com/mongodb/node-mongodb-native/commit/1c4cf6c))
27
+ * single `readPreferenceTags` should be parsed as an array ([a50611b](https://github.com/mongodb/node-mongodb-native/commit/a50611b))
28
+ * **cursor:** transforms should only be applied once to documents ([704f30a](https://github.com/mongodb/node-mongodb-native/commit/704f30a))
29
+
30
+
31
+
32
+ <a name="3.5.5"></a>
33
+ ## [3.5.5](https://github.com/mongodb/node-mongodb-native/compare/v3.5.4...v3.5.5) (2020-03-11)
34
+
35
+
36
+ ### Bug Fixes
37
+
38
+ * correctly use template string for connection string error message ([6238c84](https://github.com/mongodb/node-mongodb-native/commit/6238c84))
39
+ * don't depend on private node api for `Timeout` wrapper ([3ddaa3e](https://github.com/mongodb/node-mongodb-native/commit/3ddaa3e))
40
+ * multiple concurrent attempts to process the queue may fail ([f69f51c](https://github.com/mongodb/node-mongodb-native/commit/f69f51c))
41
+ * pass optional promise lib to maybePromise ([cde11ec](https://github.com/mongodb/node-mongodb-native/commit/cde11ec))
42
+ * **cursor:** hasNext consumes documents on cursor with limit ([ef04d00](https://github.com/mongodb/node-mongodb-native/commit/ef04d00))
43
+
44
+
45
+
46
+ <a name="3.5.4"></a>
47
+ ## [3.5.4](https://github.com/mongodb/node-mongodb-native/compare/v3.5.3...v3.5.4) (2020-02-25)
48
+
49
+
50
+ ### Bug Fixes
51
+
52
+ * **cmap:** don't run min connection thread if no minimum specified ([2d1b713](https://github.com/mongodb/node-mongodb-native/commit/2d1b713))
53
+ * **sdam:** use ObjectId comparison to track maxElectionId ([a1e0849](https://github.com/mongodb/node-mongodb-native/commit/a1e0849))
54
+ * **topology:** ensure selection wait queue is always processed ([bf701d6](https://github.com/mongodb/node-mongodb-native/commit/bf701d6))
55
+ * **topology:** enter `STATE_CLOSING` before draining waitQueue ([494dffb](https://github.com/mongodb/node-mongodb-native/commit/494dffb))
56
+ * don't consume first document when calling `hasNext` on cursor ([bb359a1](https://github.com/mongodb/node-mongodb-native/commit/bb359a1))
57
+
58
+
59
+ ### Features
60
+
61
+ * add utility helper for returning promises or using callbacks ([ac9e4c9](https://github.com/mongodb/node-mongodb-native/commit/ac9e4c9))
62
+
63
+
64
+
5
65
  <a name="3.5.3"></a>
6
66
  ## [3.5.3](https://github.com/mongodb/node-mongodb-native/compare/v3.5.2...v3.5.3) (2020-02-12)
7
67
 
package/README.md CHANGED
@@ -33,7 +33,7 @@ Core Server (i.e. SERVER) project are **public**.
33
33
 
34
34
  ### Support / Feedback
35
35
 
36
- For issues with, questions about, or feedback for the Node.js driver, please look into our [support channels](http://www.mongodb.org/about/support). Please do not email any of the driver developers directly with issues or questions - you're more likely to get an answer on the [mongodb-user](http://groups.google.com/group/mongodb-user>) list on Google Groups.
36
+ For issues with, questions about, or feedback for the Node.js driver, please look into our [support channels](https://docs.mongodb.com/manual/support). Please do not email any of the driver developers directly with issues or questions - you're more likely to get an answer on the [MongoDB Community Forums](https://community.mongodb.com/tags/c/drivers-odms-connectors/7/node-js-driver).
37
37
 
38
38
  ### Change Log
39
39
 
@@ -6,6 +6,7 @@ const MongoError = require('./core').MongoError;
6
6
  const Cursor = require('./cursor');
7
7
  const relayEvents = require('./core/utils').relayEvents;
8
8
  const maxWireVersion = require('./core/utils').maxWireVersion;
9
+ const maybePromise = require('./utils').maybePromise;
9
10
  const AggregateOperation = require('./operations/aggregate');
10
11
 
11
12
  const CHANGE_STREAM_OPTIONS = ['resumeAfter', 'startAfter', 'startAtOperationTime', 'fullDocument'];
@@ -124,10 +125,10 @@ class ChangeStream extends EventEmitter {
124
125
  * @function ChangeStream.prototype.hasNext
125
126
  * @param {ChangeStream~resultCallback} [callback] The result callback.
126
127
  * @throws {MongoError}
127
- * @return {Promise} returns Promise if no callback passed
128
+ * @returns {Promise|void} returns Promise if no callback passed
128
129
  */
129
130
  hasNext(callback) {
130
- return this.cursor.hasNext(callback);
131
+ return maybePromise(this.parent, callback, cb => this.cursor.hasNext(cb));
131
132
  }
132
133
 
133
134
  /**
@@ -135,19 +136,17 @@ class ChangeStream extends EventEmitter {
135
136
  * @function ChangeStream.prototype.next
136
137
  * @param {ChangeStream~resultCallback} [callback] The result callback.
137
138
  * @throws {MongoError}
138
- * @return {Promise} returns Promise if no callback passed
139
+ * @returns {Promise|void} returns Promise if no callback passed
139
140
  */
140
141
  next(callback) {
141
- var self = this;
142
- if (this.isClosed()) {
143
- if (callback) return callback(new Error('Change Stream is not open.'), null);
144
- return self.promiseLibrary.reject(new Error('Change Stream is not open.'));
145
- }
146
-
147
- return this.cursor
148
- .next()
149
- .then(change => processNewChange({ changeStream: self, change, callback }))
150
- .catch(error => processNewChange({ changeStream: self, error, callback }));
142
+ return maybePromise(this.parent, callback, cb => {
143
+ if (this.isClosed()) {
144
+ return cb(new Error('Change Stream is not open.'));
145
+ }
146
+ this.cursor.next((error, change) => {
147
+ processNewChange({ changeStream: this, error, change, callback: cb });
148
+ });
149
+ });
151
150
  }
152
151
 
153
152
  /**
@@ -362,7 +362,7 @@ class ConnectionPool extends EventEmitter {
362
362
  }
363
363
 
364
364
  function ensureMinPoolSize(pool) {
365
- if (pool.closed) {
365
+ if (pool.closed || pool.options.minPoolSize === 0) {
366
366
  return;
367
367
  }
368
368
 
package/lib/collection.js CHANGED
@@ -310,7 +310,7 @@ const DEPRECATED_FIND_OPTIONS = ['maxScan', 'fields', 'snapshot'];
310
310
  * @param {(ReadPreference|string)} [options.readPreference] The preferred read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST).
311
311
  * @param {boolean} [options.partial=false] Specify if the cursor should return partial results when querying against a sharded system
312
312
  * @param {number} [options.maxTimeMS] Number of milliseconds to wait before aborting the query.
313
- * @param {number} [options.maxAwaitTimeMS] The maximum amount of time for the server to wait on new documents to satisfy a tailable cursor query. Requires `taiable` and `awaitData` to be true
313
+ * @param {number} [options.maxAwaitTimeMS] The maximum amount of time for the server to wait on new documents to satisfy a tailable cursor query. Requires `tailable` and `awaitData` to be true
314
314
  * @param {boolean} [options.noCursorTimeout] The server normally times out idle cursors after an inactivity period (10 minutes) to prevent excess memory use. Set this option to prevent that.
315
315
  * @param {object} [options.collation] Specify collation (MongoDB 3.4 or higher) settings for update operation (see 3.4 documentation for available fields).
316
316
  * @param {ClientSession} [options.session] optional session to use for this operation
@@ -359,15 +359,6 @@ class CoreCursor extends Readable {
359
359
  return this.push(this.cursorState.streamOptions.transform(result));
360
360
  }
361
361
 
362
- // If we provided a map function
363
- if (
364
- this.cursorState.transforms &&
365
- typeof this.cursorState.transforms.doc === 'function' &&
366
- result != null
367
- ) {
368
- return this.push(this.cursorState.transforms.doc(result));
369
- }
370
-
371
362
  // Return the result
372
363
  this.push(result);
373
364
 
@@ -463,11 +463,13 @@ function markServerUnknown(server, error) {
463
463
  }
464
464
 
465
465
  function makeOperationHandler(server, options, callback) {
466
+ const session = options && options.session;
467
+
466
468
  return function handleOperationResult(err, result) {
467
469
  if (err) {
468
470
  if (err instanceof MongoNetworkError) {
469
- if (options && options.session) {
470
- options.session.serverSession.isDirty = true;
471
+ if (session && !session.hasEnded) {
472
+ session.serverSession.isDirty = true;
471
473
  }
472
474
 
473
475
  if (!isNetworkTimeoutError(err)) {
@@ -186,6 +186,10 @@ function readPreferenceServerSelector(readPreference) {
186
186
  );
187
187
  }
188
188
 
189
+ if (topologyDescription.type === TopologyType.Unknown) {
190
+ return [];
191
+ }
192
+
189
193
  if (
190
194
  topologyDescription.type === TopologyType.Single ||
191
195
  topologyDescription.type === TopologyType.Sharded
@@ -312,7 +312,7 @@ class Topology extends EventEmitter {
312
312
  }
313
313
 
314
314
  options = options || {};
315
- if (this.s.state === STATE_CLOSED) {
315
+ if (this.s.state === STATE_CLOSED || this.s.state === STATE_CLOSING) {
316
316
  if (typeof callback === 'function') {
317
317
  callback();
318
318
  }
@@ -320,6 +320,8 @@ class Topology extends EventEmitter {
320
320
  return;
321
321
  }
322
322
 
323
+ stateTransition(this, STATE_CLOSING);
324
+
323
325
  drainWaitQueue(this[kWaitQueue], new MongoError('Topology closed'));
324
326
  drainTimerQueue(this.s.connectionTimers);
325
327
 
@@ -336,8 +338,6 @@ class Topology extends EventEmitter {
336
338
  delete this.s.detectTopologyDescriptionChange;
337
339
  }
338
340
 
339
- stateTransition(this, STATE_CLOSING);
340
-
341
341
  this.s.sessions.forEach(session => session.endSession());
342
342
  this.s.sessionPool.endAllPooledSessions(() => {
343
343
  eachAsync(
@@ -378,6 +378,8 @@ class Topology extends EventEmitter {
378
378
  let readPreference;
379
379
  if (selector instanceof ReadPreference) {
380
380
  readPreference = selector;
381
+ } else if (typeof selector === 'string') {
382
+ readPreference = new ReadPreference(selector);
381
383
  } else {
382
384
  translateReadPreference(options);
383
385
  readPreference = options.readPreference || ReadPreference.primary;
@@ -434,8 +436,7 @@ class Topology extends EventEmitter {
434
436
  }, serverSelectionTimeoutMS);
435
437
  }
436
438
 
437
- // place the member at the front of the wait queue
438
- this[kWaitQueue].unshift(waitQueueMember);
439
+ this[kWaitQueue].push(waitQueueMember);
439
440
  processWaitQueue(this);
440
441
  }
441
442
 
@@ -520,10 +521,10 @@ class Topology extends EventEmitter {
520
521
  }
521
522
 
522
523
  // If we already know all the information contained in this updated description, then
523
- // we don't need to update anything or emit SDAM events
524
- if (previousServerDescription && previousServerDescription.equals(serverDescription)) {
525
- return;
526
- }
524
+ // we don't need to emit SDAM events, but still need to update the description, in order
525
+ // to keep client-tracked attributes like last update time and round trip time up to date
526
+ const equalDescriptions =
527
+ previousServerDescription && previousServerDescription.equals(serverDescription);
527
528
 
528
529
  // first update the TopologyDescription
529
530
  this.s.description = this.s.description.update(serverDescription);
@@ -533,15 +534,17 @@ class Topology extends EventEmitter {
533
534
  }
534
535
 
535
536
  // emit monitoring events for this change
536
- this.emit(
537
- 'serverDescriptionChanged',
538
- new events.ServerDescriptionChangedEvent(
539
- this.s.id,
540
- serverDescription.address,
541
- previousServerDescription,
542
- this.s.description.servers.get(serverDescription.address)
543
- )
544
- );
537
+ if (!equalDescriptions) {
538
+ this.emit(
539
+ 'serverDescriptionChanged',
540
+ new events.ServerDescriptionChangedEvent(
541
+ this.s.id,
542
+ serverDescription.address,
543
+ previousServerDescription,
544
+ this.s.description.servers.get(serverDescription.address)
545
+ )
546
+ );
547
+ }
545
548
 
546
549
  // update server list from updated descriptions
547
550
  updateServers(this, serverDescription);
@@ -551,14 +554,16 @@ class Topology extends EventEmitter {
551
554
  processWaitQueue(this);
552
555
  }
553
556
 
554
- this.emit(
555
- 'topologyDescriptionChanged',
556
- new events.TopologyDescriptionChangedEvent(
557
- this.s.id,
558
- previousTopologyDescription,
559
- this.s.description
560
- )
561
- );
557
+ if (!equalDescriptions) {
558
+ this.emit(
559
+ 'topologyDescriptionChanged',
560
+ new events.TopologyDescriptionChangedEvent(
561
+ this.s.id,
562
+ previousTopologyDescription,
563
+ this.s.description
564
+ )
565
+ );
566
+ }
562
567
  }
563
568
 
564
569
  auth(credentials, callback) {
@@ -980,7 +985,7 @@ function srvPollingHandler(topology) {
980
985
 
981
986
  function drainWaitQueue(queue, err) {
982
987
  while (queue.length) {
983
- const waitQueueMember = queue.pop();
988
+ const waitQueueMember = queue.shift();
984
989
  clearTimeout(waitQueueMember.timer);
985
990
  if (!waitQueueMember[kCancelled]) {
986
991
  waitQueueMember.callback(err);
@@ -994,9 +999,9 @@ function processWaitQueue(topology) {
994
999
  return;
995
1000
  }
996
1001
 
997
- const isSharded = topology.description.type === TopologyType.Sharded;
998
1002
  const serverDescriptions = Array.from(topology.description.servers.values());
999
- for (let i = 0; i < topology[kWaitQueue].length; ++i) {
1003
+ const membersToProcess = topology[kWaitQueue].length;
1004
+ for (let i = 0; i < membersToProcess && topology[kWaitQueue].length; ++i) {
1000
1005
  const waitQueueMember = topology[kWaitQueue].shift();
1001
1006
  if (waitQueueMember[kCancelled]) {
1002
1007
  continue;
@@ -1011,17 +1016,18 @@ function processWaitQueue(topology) {
1011
1016
  } catch (e) {
1012
1017
  clearTimeout(waitQueueMember.timer);
1013
1018
  waitQueueMember.callback(e);
1014
- break;
1019
+ continue;
1015
1020
  }
1016
1021
 
1017
1022
  if (selectedDescriptions.length === 0) {
1018
1023
  topology[kWaitQueue].push(waitQueueMember);
1019
- break;
1024
+ continue;
1020
1025
  }
1021
1026
 
1022
1027
  const selectedServerDescription = randomSelection(selectedDescriptions);
1023
1028
  const selectedServer = topology.s.servers.get(selectedServerDescription.address);
1024
1029
  const transaction = waitQueueMember.transaction;
1030
+ const isSharded = topology.description.type === TopologyType.Sharded;
1025
1031
  if (isSharded && transaction && transaction.isActive) {
1026
1032
  transaction.pinServer(selectedServer);
1027
1033
  }
@@ -279,6 +279,26 @@ function topologyTypeForServerType(serverType) {
279
279
  return TopologyType.ReplicaSetNoPrimary;
280
280
  }
281
281
 
282
+ function compareObjectId(oid1, oid2) {
283
+ if (oid1 == null) {
284
+ return -1;
285
+ }
286
+
287
+ if (oid2 == null) {
288
+ return 1;
289
+ }
290
+
291
+ if (oid1.id instanceof Buffer && oid2.id instanceof Buffer) {
292
+ const oid1Buffer = oid1.id;
293
+ const oid2Buffer = oid2.id;
294
+ return oid1Buffer.compare(oid2Buffer);
295
+ }
296
+
297
+ const oid1String = oid1.toString();
298
+ const oid2String = oid2.toString();
299
+ return oid1String.localeCompare(oid2String);
300
+ }
301
+
282
302
  function updateRsFromPrimary(
283
303
  serverDescriptions,
284
304
  setName,
@@ -292,11 +312,13 @@ function updateRsFromPrimary(
292
312
  return [checkHasPrimary(serverDescriptions), setName, maxSetVersion, maxElectionId];
293
313
  }
294
314
 
295
- const electionIdOID = serverDescription.electionId ? serverDescription.electionId.$oid : null;
296
- const maxElectionIdOID = maxElectionId ? maxElectionId.$oid : null;
297
- if (serverDescription.setVersion != null && electionIdOID != null) {
298
- if (maxSetVersion != null && maxElectionIdOID != null) {
299
- if (maxSetVersion > serverDescription.setVersion || maxElectionIdOID > electionIdOID) {
315
+ const electionId = serverDescription.electionId ? serverDescription.electionId : null;
316
+ if (serverDescription.setVersion && electionId) {
317
+ if (maxSetVersion && maxElectionId) {
318
+ if (
319
+ maxSetVersion > serverDescription.setVersion ||
320
+ compareObjectId(maxElectionId, electionId) > 0
321
+ ) {
300
322
  // this primary is stale, we must remove it
301
323
  serverDescriptions.set(
302
324
  serverDescription.address,
@@ -46,6 +46,8 @@ function assertAlive(session, callback) {
46
46
  * @typedef {Object} SessionId
47
47
  */
48
48
 
49
+ const kServerSession = Symbol('serverSession');
50
+
49
51
  /**
50
52
  * A class representing a client session on the server
51
53
  * WARNING: not meant to be instantiated directly.
@@ -79,8 +81,8 @@ class ClientSession extends EventEmitter {
79
81
  this.topology = topology;
80
82
  this.sessionPool = sessionPool;
81
83
  this.hasEnded = false;
82
- this.serverSession = sessionPool.acquire();
83
84
  this.clientOptions = clientOptions;
85
+ this[kServerSession] = undefined;
84
86
 
85
87
  this.supports = {
86
88
  causalConsistency:
@@ -104,6 +106,14 @@ class ClientSession extends EventEmitter {
104
106
  return this.serverSession.id;
105
107
  }
106
108
 
109
+ get serverSession() {
110
+ if (this[kServerSession] == null) {
111
+ this[kServerSession] = this.sessionPool.acquire();
112
+ }
113
+
114
+ return this[kServerSession];
115
+ }
116
+
107
117
  /**
108
118
  * Ends this session on the server
109
119
  *
@@ -123,14 +133,14 @@ class ClientSession extends EventEmitter {
123
133
  this.abortTransaction(); // pass in callback?
124
134
  }
125
135
 
136
+ // release the server session back to the pool
137
+ this.sessionPool.release(this.serverSession);
138
+ this[kServerSession] = undefined;
139
+
126
140
  // mark the session as ended, and emit a signal
127
141
  this.hasEnded = true;
128
142
  this.emit('ended', this);
129
143
 
130
- // release the server session back to the pool
131
- this.sessionPool.release(this.serverSession);
132
- this.serverSession = null;
133
-
134
144
  // spec indicates that we should ignore all errors for `endSessions`
135
145
  if (typeof callback === 'function') callback(null, null);
136
146
  }
@@ -304,6 +314,7 @@ function isUnknownTransactionCommitResult(err) {
304
314
  }
305
315
 
306
316
  function isMaxTimeMSExpiredError(err) {
317
+ if (err == null) return false;
307
318
  return (
308
319
  err.code === MAX_TIME_MS_EXPIRED_CODE ||
309
320
  (err.writeConcernError && err.writeConcernError.code === MAX_TIME_MS_EXPIRED_CODE)
@@ -691,13 +702,12 @@ function commandSupportsReadConcern(command, options) {
691
702
  * @return {MongoError|null} An error, if some error condition was met
692
703
  */
693
704
  function applySession(session, command, options) {
694
- const serverSession = session.serverSession;
695
- if (serverSession == null) {
705
+ if (session.hasEnded) {
696
706
  // TODO: merge this with `assertAlive`, did not want to throw a try/catch here
697
707
  return new MongoError('Cannot use a session that has ended');
698
708
  }
699
709
 
700
- // mark the last use of this session, and apply the `lsid`
710
+ const serverSession = session.serverSession;
701
711
  serverSession.lastUse = Date.now();
702
712
  command.lsid = serverSession.id;
703
713
 
@@ -211,10 +211,18 @@ function Interval(fn, time) {
211
211
 
212
212
  function Timeout(fn, time) {
213
213
  var timer = false;
214
+ var func = () => {
215
+ if (timer) {
216
+ clearTimeout(timer);
217
+ timer = false;
218
+
219
+ fn();
220
+ }
221
+ };
214
222
 
215
223
  this.start = function() {
216
224
  if (!this.isRunning()) {
217
- timer = setTimeout(fn, time);
225
+ timer = setTimeout(func, time);
218
226
  }
219
227
  return this;
220
228
  };
@@ -226,7 +234,6 @@ function Timeout(fn, time) {
226
234
  };
227
235
 
228
236
  this.isRunning = function() {
229
- if (timer && timer._called) return false;
230
237
  return timer !== false;
231
238
  };
232
239
  }
@@ -285,8 +285,8 @@ function applyConnectionStringOption(obj, key, value, options) {
285
285
  }
286
286
  }
287
287
 
288
- if (key === 'readpreferencetags' && Array.isArray(value)) {
289
- value = splitArrayOfMultipleReadPreferenceTags(value);
288
+ if (key === 'readpreferencetags') {
289
+ value = Array.isArray(value) ? splitArrayOfMultipleReadPreferenceTags(value) : [value];
290
290
  }
291
291
 
292
292
  // set the actual value
@@ -510,7 +510,7 @@ function assertTlsOptionsAreEqual(optionName, queryString, queryStringKeys) {
510
510
  const firstValue = queryString[optionName][0];
511
511
  queryString[optionName].forEach(tlsValue => {
512
512
  if (tlsValue !== firstValue) {
513
- throw new MongoParseError('All values of ${optionName} must be the same.');
513
+ throw new MongoParseError(`All values of ${optionName} must be the same.`);
514
514
  }
515
515
  });
516
516
  }
package/lib/core/utils.js CHANGED
@@ -255,6 +255,8 @@ function makeClientMetadata(options) {
255
255
  return metadata;
256
256
  }
257
257
 
258
+ const noop = () => {};
259
+
258
260
  module.exports = {
259
261
  uuidV4,
260
262
  calculateDurationInMs,
@@ -270,5 +272,6 @@ module.exports = {
270
272
  tagsStrictEqual,
271
273
  errorStrictEqual,
272
274
  makeStateMachine,
273
- makeClientMetadata
275
+ makeClientMetadata,
276
+ noop
274
277
  };