event-storage 1.2.0 → 1.3.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/README.md CHANGED
@@ -1,4 +1,4 @@
1
- ![event-storage](logo/color.png)
1
+ ![event-storage](logo/color.svg)
2
2
 
3
3
  [![build](https://github.com/albe/node-event-storage/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/albe/node-event-storage/actions/workflows/build.yml)
4
4
  [![npm version](https://badge.fury.io/js/event-storage.svg)](https://badge.fury.io/js/event-storage)
@@ -62,8 +62,8 @@ eventstore.on('ready', () => {
62
62
  | **Optimistic concurrency** | Pass `expectedVersion` to `commit()` to guarantee conflict-free writes. |
63
63
  | **Flexible stream reading** | Range queries, reverse iteration, and a fluent builder API. |
64
64
  | **Derived streams** | Filter or combine events into new read-only streams. |
65
- | **Multi-value matchers** | Object matchers support array values (OR semantics) and still benefit from O(1) discriminant routing on writes. |
66
- | **DCB / `typeAccessor`** | Configure `typeAccessor` to have per-type stream indexes maintained automatically, and use `query()` / `Condition` for fine-grained, query-scoped optimistic concurrency (Dynamic Consistency Boundaries). |
65
+ | **Object matchers** | Support nested equality, array values (OR semantics), and scalar operators like `$gt` / `$gte` / `$lt` / `$lte` / `$eq` / `$ne`, while still benefiting from O(1) discriminant routing on writes. |
66
+ | **DCB** | Configure `typeAccessor` to have per-type stream indexes maintained automatically, and use `query()` / `Condition` for fine-grained, query-scoped optimistic concurrency (Dynamic Consistency Boundaries). |
67
67
  | **Stream categories** | Name streams `<category>-<id>` and query the whole category at once. |
68
68
  | **Durable consumers** | At-least-once (and exactly-once with `setState`) event delivery with automatic position tracking. |
69
69
  | **Consistency guards** | Build aggregates that enforce business invariants with built-in snapshotting. |
@@ -75,6 +75,30 @@ eventstore.on('ready', () => {
75
75
 
76
76
  ---
77
77
 
78
+ ## DCB Example
79
+
80
+ ```javascript
81
+ const { stream, condition } = store.query(['OrderPlaced'], { payload: { customerId: 'cust-1' } });
82
+ const hasOpenOrder = stream.some((event) => event.status === 'open');
83
+
84
+ if (!hasOpenOrder) {
85
+ store.commit('orders-cust-1', [{ type: 'OrderPlaced', customerId: 'cust-1' }], condition);
86
+ }
87
+ ```
88
+
89
+ ---
90
+
91
+ ## Object Matcher Syntax
92
+
93
+ Object matchers support nested equality, array values, and scalar operators like `$gt`, `$gte`, `$lt`, `$lte`, `$eq`, and `$ne`.
94
+
95
+ ```javascript
96
+ { payload: { type: ['OrderPlaced', 'OrderCancelled'] } }
97
+ { payload: { amount: { $gte: 100, $lt: 1000 } } }
98
+ ```
99
+
100
+ ---
101
+
78
102
  ## HTTP API
79
103
 
80
104
  To expose an event store over HTTP, see the companion package **[event-storage-http](https://github.com/albe/node-event-storage-http)**:
@@ -102,7 +126,7 @@ The full documentation is hosted at **<https://node-event-storage.readthedocs.io
102
126
 
103
127
  - [Getting Started](https://node-event-storage.readthedocs.io/en/latest/getting-started/) — installation, constructor options, basic usage.
104
128
  - [Event Streams](https://node-event-storage.readthedocs.io/en/latest/streams/) — writing, reading, optimistic concurrency, fluent API, joining streams, categories, and event metadata.
105
- - [Dynamic Consistency Boundaries (DCB)](https://node-event-storage.readthedocs.io/en/latest/dcb/) — `typeAccessor`, multi-value matchers, consistency tokens, and the full DCB workflow.
129
+ - [Dynamic Consistency Boundaries (DCB)](https://node-event-storage.readthedocs.io/en/latest/dcb/) — `typeAccessor`, query matchers, consistency tokens, and the full DCB workflow.
106
130
  - [Consumers](https://node-event-storage.readthedocs.io/en/latest/consumers/) — at-least-once and exactly-once delivery, consumer state, consistency guards, and read-only mode.
107
131
  - [Advanced Topics](https://node-event-storage.readthedocs.io/en/latest/advanced/) — ACID properties, reliability and crash-safety guarantees, storage configuration, partitioning, custom serialization, compression, security, and access control hooks.
108
132
 
package/index.js CHANGED
@@ -3,3 +3,4 @@ export { default as EventStream } from './src/EventStream.js';
3
3
  export { default as Storage, StorageLockedError } from './src/Storage.js';
4
4
  export { default as Index } from './src/Index.js';
5
5
  export { default as Consumer } from './src/Consumer.js';
6
+ export { matches, buildRawBufferMatcher } from './src/utils/metadataUtil.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "event-storage",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "type": "module",
5
5
  "description": "An optimized embedded event store for node.js",
6
6
  "keywords": [
@@ -26,6 +26,7 @@
26
26
  },
27
27
  "scripts": {
28
28
  "test": "c8 --reporter=lcov --reporter=text mocha test/*.spec.js",
29
+ "test:grep": "c8 --reporter=lcov --reporter=text mocha test/*.spec.js --grep",
29
30
  "coverage": "c8 report --reporter=text-lcov | coveralls"
30
31
  },
31
32
  "files": [
package/src/Consumer.js CHANGED
@@ -3,6 +3,7 @@ import fs from 'fs';
3
3
  import path from 'path';
4
4
  import { assert } from './utils/util.js';
5
5
  import { ensureDirectory } from './utils/fsUtil.js';
6
+ import { normalizeConsumerStateArgs } from './utils/apiHelpers.js';
6
7
  import Storage from './Storage/ReadableStorage.js';
7
8
  const MAX_CATCHUP_BATCH = 10;
8
9
 
@@ -11,7 +12,7 @@ const MAX_CATCHUP_BATCH = 10;
11
12
  * @param {string} filename
12
13
  */
13
14
  const safeUnlink = (filename) => {
14
- /* istanbul ignore next */
15
+ /* c8 ignore next */
15
16
  try {
16
17
  fs.unlinkSync(filename);
17
18
  } catch (e) {
@@ -84,14 +85,11 @@ class Consumer extends stream.Readable {
84
85
  * @param {number} startFrom The revision to start from within the index to consume.
85
86
  */
86
87
  restoreState(initialState, startFrom) {
87
- /* istanbul ignore if */
88
+ /* c8 ignore next 3 */
88
89
  if (!this.fileName) {
89
90
  return;
90
91
  }
91
- if (typeof initialState === 'number') {
92
- startFrom = initialState;
93
- initialState = {};
94
- }
92
+ ({ initialState, startFrom } = normalizeConsumerStateArgs(initialState, startFrom));
95
93
  try {
96
94
  const consumerData = fs.readFileSync(this.fileName);
97
95
  this.position = consumerData.readInt32LE(0);
@@ -137,7 +135,6 @@ class Consumer extends stream.Readable {
137
135
  return;
138
136
  }
139
137
 
140
- /* istanbul ignore if */
141
138
  if (this.position !== position - 1) {
142
139
  return;
143
140
  }
@@ -171,7 +168,7 @@ class Consumer extends stream.Readable {
171
168
  consumerData.write(consumerState, 4, consumerState.length, 'utf-8');
172
169
  const tmpFile = this.fileName + '.' + this.position;
173
170
  this.persisting = null;
174
- /* istanbul ignore if */
171
+ /* c8 ignore next 3 */
175
172
  if (fs.existsSync(tmpFile)) {
176
173
  throw new Error(`Trying to update consumer ${this.name} concurrently. Keep each single consumer within a single process.`);
177
174
  }
@@ -181,7 +178,7 @@ class Consumer extends stream.Readable {
181
178
  fs.renameSync(tmpFile, this.fileName);
182
179
  this.emit('persisted', consumerState);
183
180
  } catch (e) {
184
- /* istanbul ignore next */
181
+ /* c8 ignore next */
185
182
  safeUnlink(tmpFile);
186
183
  }
187
184
  });
@@ -274,10 +271,7 @@ class Consumer extends stream.Readable {
274
271
  * @api
275
272
  */
276
273
  reset(initialState = {}, startFrom = 0) {
277
- if (typeof initialState === 'number') {
278
- startFrom = initialState;
279
- initialState = {};
280
- }
274
+ ({ initialState, startFrom } = normalizeConsumerStateArgs(initialState, startFrom));
281
275
  const restart = this.consuming;
282
276
  this.stop();
283
277
  this.state = Object.freeze(initialState);
package/src/EventStore.js CHANGED
@@ -9,6 +9,7 @@ import Consumer from './Consumer.js';
9
9
  import { assert, getPropertyAtPath } from './utils/util.js';
10
10
  import { ensureDirectory, scanForFiles } from './utils/fsUtil.js';
11
11
  import { buildTypeMatcherFn } from './utils/metadataUtil.js';
12
+ import { fixCommitArgumentTypes, parseStreamFromIndexName, normalizePredicateRaw } from './utils/apiHelpers.js';
12
13
 
13
14
  const ExpectedVersion = {
14
15
  Any: -1,
@@ -185,7 +186,6 @@ class EventStore extends events.EventEmitter {
185
186
  * @param {string} name The full stream name, including the `stream-` prefix (and optional `.closed` suffix).
186
187
  */
187
188
  registerStream(name) {
188
- /* istanbul ignore if */
189
189
  if (!name.startsWith('stream-')) {
190
190
  return;
191
191
  }
@@ -343,39 +343,6 @@ class EventStore extends events.EventEmitter {
343
343
  return this.storage.length;
344
344
  }
345
345
 
346
- /**
347
- * This method makes it so the last three arguments can be given either as:
348
- * - expectedVersion, metadata, callback
349
- * - expectedVersion, callback
350
- * - metadata, callback
351
- * - callback
352
- *
353
- * @private
354
- * @param {Array<object>|object} events
355
- * @param {number|CommitCondition} [expectedVersion]
356
- * @param {object|function} [metadata]
357
- * @param {function} [callback]
358
- * @returns {{events: Array<object>, metadata: object, callback: function, expectedVersion: number|CommitCondition}}
359
- */
360
- static fixArgumentTypes(events, expectedVersion, metadata, callback) {
361
- if (!(events instanceof Array)) {
362
- events = [events];
363
- }
364
- if (typeof expectedVersion !== 'number' && !(expectedVersion instanceof CommitCondition)) {
365
- callback = metadata;
366
- metadata = expectedVersion;
367
- expectedVersion = ExpectedVersion.Any;
368
- }
369
- if (typeof metadata !== 'object') {
370
- callback = metadata;
371
- metadata = {};
372
- }
373
- if (typeof callback !== 'function') {
374
- callback = () => {};
375
- }
376
- return { events, expectedVersion, metadata, callback };
377
- }
378
-
379
346
  /**
380
347
  * Check a {@link CommitCondition} against the current state of the store.
381
348
  * Iterates a join stream over all condition type streams starting from
@@ -383,6 +350,7 @@ class EventStore extends events.EventEmitter {
383
350
  * {@link OptimisticConcurrencyError} when a new event of a listed type satisfies
384
351
  * `condition.matcher(payload, metadata)` (or any such event when no matcher is provided).
385
352
  *
353
+ * @private
386
354
  * @param {CommitCondition} condition
387
355
  * @throws {OptimisticConcurrencyError}
388
356
  */
@@ -410,6 +378,7 @@ class EventStore extends events.EventEmitter {
410
378
  * Ensure a dedicated type stream exists for each event's type, creating it if needed.
411
379
  * Must be called before the entity stream is created to guarantee correct index routing.
412
380
  *
381
+ * @private
413
382
  * @param {Array<object>} events The events to process.
414
383
  */
415
384
  ensureTypeStreams(events) {
@@ -425,6 +394,11 @@ class EventStore extends events.EventEmitter {
425
394
  }
426
395
  }
427
396
 
397
+ /**
398
+ * @private
399
+ * @param {object} event
400
+ * @returns {string|null}
401
+ */
428
402
  resolveValidatedTypeStreamName(event) {
429
403
  const type = this.typeAccessor(event);
430
404
  if (type === undefined || type === null || type === '') {
@@ -435,6 +409,11 @@ class EventStore extends events.EventEmitter {
435
409
  return type;
436
410
  }
437
411
 
412
+ /**
413
+ * @private
414
+ * @param {string[]} types
415
+ * @returns {string[]}
416
+ */
438
417
  getExistingQueryTypes(types) {
439
418
  const queryTypes = [];
440
419
  for (const type of types) {
@@ -469,7 +448,14 @@ class EventStore extends events.EventEmitter {
469
448
  assert(typeof streamName === 'string' && streamName !== '', 'Must specify a stream name for commit.');
470
449
  assert(typeof events !== 'undefined' && events !== null, 'No events specified for commit.');
471
450
 
472
- ({ events, expectedVersion, metadata, callback } = EventStore.fixArgumentTypes(events, expectedVersion, metadata, callback));
451
+ ({ events, expectedVersion, metadata, callback } = fixCommitArgumentTypes(
452
+ events,
453
+ expectedVersion,
454
+ metadata,
455
+ callback,
456
+ ExpectedVersion.Any,
457
+ CommitCondition
458
+ ));
473
459
 
474
460
  // Perform DCB-style concurrency check when a CommitCondition is provided.
475
461
  if (expectedVersion instanceof CommitCondition) {
@@ -782,7 +768,13 @@ class EventStore extends events.EventEmitter {
782
768
  }
783
769
  const streamName = streamNameOrIdentifier;
784
770
  if (this.consumers.has(identifier)) {
785
- return this.consumers.get(identifier);
771
+ const existingConsumer = this.consumers.get(identifier);
772
+ if (existingConsumer.streamName === streamName) {
773
+ return existingConsumer;
774
+ }
775
+ // Rebind identifier to the requested stream when a consumer with the same
776
+ // identifier already exists for another stream.
777
+ existingConsumer.stop();
786
778
  }
787
779
  const consumer = new Consumer(this.storage, streamName === '_all' ? '_all' : 'stream-' + streamName, identifier, initialState, since);
788
780
  consumer.streamName = streamName;
@@ -829,22 +821,6 @@ class EventStore extends events.EventEmitter {
829
821
  }
830
822
  }
831
823
 
832
- function parseStreamFromIndexName(indexName) {
833
- if (indexName === '_all') {
834
- return '_all';
835
- }
836
- if (indexName.startsWith('stream-')) {
837
- return indexName.slice(7);
838
- }
839
- return indexName;
840
- }
841
-
842
- function normalizePredicateRaw(predicate, raw) {
843
- if (typeof predicate === 'boolean' && raw === false) {
844
- return { predicate: null, raw: predicate };
845
- }
846
- return { predicate, raw };
847
- }
848
824
 
849
825
  EventStore.Storage = Storage;
850
826
  EventStore.Index = Index;
@@ -1,30 +1,10 @@
1
1
  import stream from 'stream';
2
2
  import { assert } from './utils/util.js';
3
3
  import { buildRawBufferMatcher, matches } from './utils/metadataUtil.js';
4
+ import { normalizeRevision, normalizeMaxRevision } from './utils/apiHelpers.js';
4
5
 
5
6
  const NDJSON_NEWLINE = Buffer.from('\n');
6
7
 
7
- /**
8
- * Calculate the actual version number from a possibly relative (negative) version number.
9
- *
10
- * @param {number} version The version to normalize.
11
- * @param {number} length The maximum version number
12
- * @returns {number} The absolute version number.
13
- */
14
- function normalizeVersion(version, length) {
15
- return version < 0 ? version + length + 1 : version;
16
- }
17
-
18
- /**
19
- * Return the lower absolute version given a version and a maxVersion constraint.
20
- * @param {number} version
21
- * @param {number} maxVersion
22
- * @returns {number}
23
- */
24
- function minVersion(version, maxVersion) {
25
- return Math.min(version, maxVersion < 0 ? version + maxVersion + 1 : maxVersion);
26
- }
27
-
28
8
  /**
29
9
  * An event stream is a simple wrapper around an iterator over storage documents.
30
10
  * It implements a node readable stream interface.
@@ -56,9 +36,9 @@ class EventStream extends stream.Readable {
56
36
  this.rawMatcher = null;
57
37
  if (eventStore.streams[name]) {
58
38
  this.streamIndex = eventStore.streams[name].index;
59
- this.minRevision = normalizeVersion(minRevision, this.streamIndex.length);
60
- this.maxRevision = normalizeVersion(maxRevision, this.streamIndex.length);
61
- this.version = minVersion(this.streamIndex.length, maxRevision);
39
+ this.minRevision = normalizeRevision(minRevision, this.streamIndex.length);
40
+ this.maxRevision = normalizeRevision(maxRevision, this.streamIndex.length);
41
+ this.version = normalizeMaxRevision(this.streamIndex.length, maxRevision);
62
42
  this._iterator = null;
63
43
  this.fetch = () => eventStore.storage.readRange(this.minRevision, this.maxRevision, this.streamIndex, raw);
64
44
  } else {
@@ -74,7 +54,7 @@ class EventStream extends stream.Readable {
74
54
  * @returns {EventStream}
75
55
  */
76
56
  from(revision) {
77
- this.minRevision = normalizeVersion(revision, this.streamIndex.length);
57
+ this.minRevision = normalizeRevision(revision, this.streamIndex.length);
78
58
  return this;
79
59
  }
80
60
 
@@ -84,8 +64,8 @@ class EventStream extends stream.Readable {
84
64
  * @returns {EventStream}
85
65
  */
86
66
  until(revision) {
87
- this.maxRevision = normalizeVersion(revision, this.streamIndex.length);
88
- this.version = minVersion(this.streamIndex.length, this.maxRevision);
67
+ this.maxRevision = normalizeRevision(revision, this.streamIndex.length);
68
+ this.version = normalizeMaxRevision(this.streamIndex.length, this.maxRevision);
89
69
  return this;
90
70
  }
91
71
 
@@ -169,7 +149,7 @@ class EventStream extends stream.Readable {
169
149
  let tmp = this.maxRevision;
170
150
  this.maxRevision = this.minRevision;
171
151
  this.minRevision = tmp;
172
- this.version = minVersion(this.streamIndex.length, this.maxRevision);
152
+ this.version = normalizeMaxRevision(this.streamIndex.length, this.maxRevision);
173
153
  return this;
174
154
  }
175
155
 
@@ -20,7 +20,7 @@ class ReadOnlyIndex extends watchesFile(ReadableIndex) {
20
20
  * @param {string} filename
21
21
  */
22
22
  onChange(filename) {
23
- /* istanbul ignore if */
23
+ /* c8 ignore next 3 */
24
24
  if (!this.fd) {
25
25
  return;
26
26
  }
@@ -3,6 +3,7 @@ import path from 'path';
3
3
  import events from 'events';
4
4
  import Entry, { assertValidEntryClass } from '../IndexEntry.js';
5
5
  import { assert, wrapAndCheck, binarySearch } from '../utils/util.js';
6
+ import { normalizeNamedCtorArgs } from '../utils/apiHelpers.js';
6
7
 
7
8
  // node-event-store-index V01
8
9
  const HEADER_MAGIC = "nesidx01";
@@ -44,10 +45,7 @@ class ReadableIndex extends events.EventEmitter {
44
45
  */
45
46
  constructor(name = '.index', options = {}) {
46
47
  super();
47
- if (typeof name !== 'string') {
48
- options = name;
49
- name = '.index';
50
- }
48
+ ({ name, options } = normalizeNamedCtorArgs(name, options, '.index'));
51
49
  let defaults = {
52
50
  dataDirectory: '.',
53
51
  EntryClass: Entry
@@ -3,6 +3,7 @@ import ReadableIndex, { Entry, CorruptedIndexError, HEADER_MAGIC } from './Reada
3
3
  import { assert, assertEqual } from '../utils/util.js';
4
4
  import { buildMetadataHeader } from '../utils/metadataUtil.js';
5
5
  import { ensureDirectory } from '../utils/fsUtil.js';
6
+ import { normalizeNamedCtorArgs } from '../utils/apiHelpers.js';
6
7
 
7
8
  /**
8
9
  * An index is a simple append-only file that stores an ordered list of entry elements pointing to the actual file position
@@ -27,10 +28,7 @@ class WritableIndex extends ReadableIndex {
27
28
  * @param {object} [options.metadata] An object containing the metadata information for this index. Will be written on initial creation and checked on subsequent openings.
28
29
  */
29
30
  constructor(name = '.index', options = {}) {
30
- if (typeof name !== 'string') {
31
- options = name;
32
- name = '.index';
33
- }
31
+ ({ name, options } = normalizeNamedCtorArgs(name, options, '.index'));
34
32
  let defaults = {
35
33
  writeBufferSize: 4096,
36
34
  flushDelay: 100,
@@ -1,20 +1,10 @@
1
1
  import EventStream from './EventStream.js';
2
2
  import { assert, kWayMerge } from './utils/util.js';
3
+ import { normalizeRevision } from './utils/apiHelpers.js';
3
4
 
4
5
  /** Reusable sentinel used for missing or empty per-stream iterators. */
5
6
  const emptyIterator = Object.freeze({ next() { return { done: true }; } });
6
7
 
7
- /**
8
- * Calculate the actual version number from a possibly relative (negative) version number.
9
- *
10
- * @param {number} version The version to normalize.
11
- * @param {number} length The maximum version number
12
- * @returns {number} The absolute version number.
13
- */
14
- function normalizeVersion(version, length) {
15
- return version < 0 ? version + length + 1 : version;
16
- }
17
-
18
8
  /**
19
9
  * An event stream is a simple wrapper around an iterator over storage documents.
20
10
  * It implements a node readable stream interface.
@@ -36,8 +26,8 @@ class JoinEventStream extends EventStream {
36
26
 
37
27
  this.streamIndex = eventStore.storage.index;
38
28
  // Translate revisions to index numbers (1-based) and wrap around negatives
39
- this.minRevision = normalizeVersion(minRevision, eventStore.length);
40
- this.maxRevision = normalizeVersion(maxRevision, eventStore.length);
29
+ this.minRevision = normalizeRevision(minRevision, eventStore.length);
30
+ this.maxRevision = normalizeRevision(maxRevision, eventStore.length);
41
31
  this.fetch = function() {
42
32
  return streams.map(streamName => {
43
33
  const streamIndex = eventStore.streams[streamName]?.index;
@@ -18,7 +18,7 @@ class ReadOnlyPartition extends WatchesFile(ReadablePartition) {
18
18
  * @param {string} filename
19
19
  */
20
20
  onChange(filename) {
21
- /* istanbul ignore if */
21
+ /* c8 ignore next 3 */
22
22
  if (!this.fd) {
23
23
  return;
24
24
  }
@@ -130,7 +130,7 @@ class ReadablePartition extends events.EventEmitter {
130
130
  const metadata = metadataBuffer.toString('utf8').trim();
131
131
  try {
132
132
  this.metadata = JSON.parse(metadata);
133
- this.metadata.epoch = this.metadata.epoch /* istanbul ignore next */|| NES_EPOCH.getTime();
133
+ this.metadata.epoch = this.metadata.epoch /* c8 ignore next */|| NES_EPOCH.getTime();
134
134
  } catch (e) {
135
135
  throw new Error('Invalid metadata.');
136
136
  }
@@ -373,13 +373,13 @@ class ReadablePartition extends events.EventEmitter {
373
373
  */
374
374
  readDocumentBefore(position) {
375
375
  const docPos = this.findDocumentPositionBefore(position);
376
- /* istanbul ignore if */
376
+ /* c8 ignore next */
377
377
  if (docPos === false || docPos < 0) return null;
378
378
  const reader = this.prepareReadBufferBackwards(Math.min(docPos + (this.readBuffer.byteLength >> 1), this.size));
379
- /* istanbul ignore if */
379
+ /* c8 ignore next */
380
380
  if (!reader.buffer) return null;
381
381
  const cursor = docPos - this.readBufferPos;
382
- /* istanbul ignore if */
382
+ /* c8 ignore next */
383
383
  if (cursor < 0 || cursor + DOCUMENT_HEADER_SIZE > reader.length) return null;
384
384
  const header = this.readDocumentHeader(reader.buffer, cursor, docPos);
385
385
  return { header, position: docPos };
@@ -436,7 +436,7 @@ class ReadablePartition extends events.EventEmitter {
436
436
 
437
437
  const header = {};
438
438
  const data = this.readFrom(position, 0, header);
439
- /* istanbul ignore if */
439
+ /* c8 ignore next 3 */
440
440
  if (data === false) {
441
441
  return null;
442
442
  }
@@ -178,7 +178,7 @@ class WritablePartition extends ReadablePartition {
178
178
  */
179
179
  writeDocumentHeader(buffer, offset, dataSize, sequenceNumber = null, time64 = null) {
180
180
  ({ sequenceNumber, time64 } = this.normalizeWriteMetadata(sequenceNumber, time64));
181
- /* istanbul ignore if */
181
+ /* c8 ignore next */
182
182
  assert(time64 >= 0, 'Time may not be negative!');
183
183
 
184
184
  buffer.writeUInt32BE(dataSize, offset);
@@ -91,7 +91,7 @@ class ReadOnlyStorage extends ReadableStorage {
91
91
  return;
92
92
  }
93
93
  const entries = index.range(prevLength + 1, newLength);
94
- /* istanbul ignore if */
94
+ /* c8 ignore next 3 */
95
95
  if (entries === false) {
96
96
  return;
97
97
  }
@@ -1,16 +1,16 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  import events from 'events';
4
- import Partition, { ReadOnly as ReadOnlyPartition } from '../Partition.js';
5
- import Index, { ReadOnly as ReadOnlyIndex } from '../Index.js';
4
+ import { ReadOnly as ReadOnlyPartition } from '../Partition.js';
5
+ import { ReadOnly as ReadOnlyIndex } from '../Index.js';
6
6
  import { assert, wrapAndCheck, iterate, kWayMerge } from '../utils/util.js';
7
7
  import { scanForFiles } from '../utils/fsUtil.js';
8
8
  import { createHmac, matches, buildMetadataForMatcher } from '../utils/metadataUtil.js';
9
+ import { normalizeNamedCtorArgs } from '../utils/apiHelpers.js';
9
10
  import IndexMatcher from '../IndexMatcher.js';
10
11
  import PartitionPool from '../PartitionPool.js';
11
12
 
12
13
  const DEFAULT_READ_BUFFER_SIZE = 4 * 1024;
13
- const NDJSON_NEWLINE = Buffer.from('\n');
14
14
 
15
15
  /**
16
16
  * Default ordered list of document property paths used as discriminant keys when
@@ -60,10 +60,7 @@ class ReadableStorage extends events.EventEmitter {
60
60
  */
61
61
  constructor(storageName = 'storage', config = {}) {
62
62
  super();
63
- if (typeof storageName !== 'string') {
64
- config = storageName;
65
- storageName = undefined;
66
- }
63
+ ({ name: storageName, options: config } = normalizeNamedCtorArgs(storageName, config));
67
64
 
68
65
  this.storageFile = storageName || 'storage';
69
66
  const defaults = {
@@ -158,7 +155,7 @@ class ReadableStorage extends events.EventEmitter {
158
155
  const partition = this.createPartition(file, this.partitionConfig);
159
156
  this.partitions.add(partition.id, partition);
160
157
  }, (partErr) => {
161
- /* istanbul ignore if */
158
+ /* c8 ignore next */
162
159
  if (partErr) throw partErr;
163
160
 
164
161
  // Scan was cancelled by close() between the two scan phases.
@@ -173,7 +170,7 @@ class ReadableStorage extends events.EventEmitter {
173
170
  this.emit('index-created', name);
174
171
  }, (indexErr) => {
175
172
  // The directory could disappear between existsSync and readdir (e.g. test cleanup).
176
- /* istanbul ignore if */
173
+ /* c8 ignore next */
177
174
  if (indexErr && indexErr.code !== 'ENOENT') throw indexErr;
178
175
  done();
179
176
  });
@@ -481,7 +478,7 @@ class ReadableStorage extends events.EventEmitter {
481
478
  * @param {boolean} [noIndex=false] When true, bypasses the index and iterates partitions directly.
482
479
  */
483
480
  forEachDocument(iterationHandler, noIndex = false) {
484
- /* istanbul ignore if */
481
+ /* c8 ignore next 3 */
485
482
  if (typeof iterationHandler !== 'function') {
486
483
  return;
487
484
  }
@@ -512,7 +509,7 @@ class ReadableStorage extends events.EventEmitter {
512
509
  * @param {object} [matchDocument] If supplied, only indexes the document matches on will be iterated.
513
510
  */
514
511
  forEachSecondaryIndex(iterationHandler, matchDocument) {
515
- /* istanbul ignore if */
512
+ /* c8 ignore next 3 */
516
513
  if (typeof iterationHandler !== 'function') {
517
514
  return;
518
515
  }
@@ -537,7 +534,7 @@ class ReadableStorage extends events.EventEmitter {
537
534
  * @param {function(ReadablePartition)} iterationHandler
538
535
  */
539
536
  forEachPartition(iterationHandler) {
540
- /* istanbul ignore if */
537
+ /* c8 ignore next 3 */
541
538
  if (typeof iterationHandler !== 'function') {
542
539
  return;
543
540
  }