• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

albe / node-event-storage / 23897068196

02 Apr 2026 10:56AM UTC coverage: 98.151% (+0.1%) from 98.054%
23897068196

Pull #257

github

web-flow
Merge b52671064 into 50d3642f2
Pull Request #257: Move to ES Modules (ESM)

890 of 929 branches covered (95.8%)

Branch coverage included in aggregate %.

118 of 118 new or added lines in 21 files covered. (100.0%)

55 existing lines in 12 files now uncovered.

4684 of 4750 relevant lines covered (98.61%)

787.62 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.72
/src/Partition/ReadablePartition.js
1
import fs from 'fs';
4✔
2
import path from 'path';
4✔
3
import events from 'events';
4✔
4
import { assert, alignTo, hash, binarySearch } from '../util.js';
4✔
5

4✔
6
const DEFAULT_READ_BUFFER_SIZE = 64 * 1024;
4✔
7
const DOCUMENT_HEADER_SIZE = 16;
4✔
8
const DOCUMENT_ALIGNMENT = 4;
4✔
9
const DOCUMENT_SEPARATOR = "\x00\x00\x1E\n";
4✔
10
const DOCUMENT_FOOTER_SIZE = 4 /* additional data size footer */ + DOCUMENT_SEPARATOR.length;
4✔
11

4✔
12
// node-event-store partition V03
4✔
13
const HEADER_MAGIC = "nesprt03";
4✔
14

4✔
15
const NES_EPOCH = new Date('2020-01-01T00:00:00');
4✔
16

4✔
17
class CorruptFileError extends Error {}
4✔
18
class InvalidDataSizeError extends Error {}
4✔
19

4✔
20
/**
4✔
21
 * A partition is a single file where the storage will write documents to depending on some partitioning rules.
4✔
22
 * In the case of an event store, this is most likely the (write) streams.
4✔
23
 */
4✔
24
class ReadablePartition extends events.EventEmitter {
4✔
25

4✔
26
    /**
4✔
27
     * Get the id for a specific partition name.
4✔
28
     *
4✔
29
     * @param {string} name
4✔
30
     * @returns {number}
4✔
31
     */
4✔
32
    static idFor(name) {
4✔
33
        return hash(name);
4,420✔
34
    }
4,420✔
35

4✔
36
    /**
4✔
37
     * @param {string} name The name of the partition.
4✔
38
     * @param {object} [config] An object with storage parameters.
4✔
39
     * @param {string} [config.dataDirectory] The path where the storage data should reside. Default '.'.
4✔
40
     * @param {number} [config.readBufferSize] Size of the read buffer in bytes. Default 4096.
4✔
41
     */
4✔
42
    constructor(name, config = {}) {
4✔
43
        super();
1,456✔
44
        assert(typeof name === 'string' && name !== '', 'Must specify a partition name.');
1,456✔
45

1,456✔
46
        let defaults = {
1,456✔
47
            dataDirectory: '.',
1,456✔
48
            readBufferSize: DEFAULT_READ_BUFFER_SIZE
1,456✔
49
        };
1,456✔
50
        config = Object.assign(defaults, config);
1,456✔
51
        this.dataDirectory = path.resolve(config.dataDirectory);
1,456✔
52

1,456✔
53
        this.name = name;
1,456✔
54
        this.id = ReadablePartition.idFor(name);
1,456✔
55
        this.fileName = path.resolve(this.dataDirectory, this.name);
1,456✔
56
        this.fileMode = 'r';
1,456✔
57
        this.headerSize = 0;
1,456✔
58

1,456✔
59
        this.readBufferSize = config.readBufferSize >>> 0;  // jshint ignore:line
1,456✔
60
    }
1,456✔
61

4✔
62
    /**
4✔
63
     * Check if the partition file is opened.
4✔
64
     *
4✔
65
     * @returns {boolean}
4✔
66
     */
4✔
67
    isOpen() {
4✔
68
        return !!this.fd;
120✔
69
    }
120✔
70

4✔
71
    /**
4✔
72
     * Open the partition storage and create read buffers.
4✔
73
     *
4✔
74
     * @api
4✔
75
     * @returns {boolean} Returns false if the file is not a valid partition.
4✔
76
     */
4✔
77
    open() {
4✔
78
        if (this.fd) {
2,820✔
79
            return true;
16✔
80
        }
16✔
81

2,804✔
82
        this.fd = fs.openSync(this.fileName, this.fileMode);
2,804✔
83

2,804✔
84
        // allocUnsafeSlow because we don't need buffer pooling for these relatively long-lived buffers
2,804✔
85
        this.readBuffer = Buffer.allocUnsafeSlow(this.readBufferSize);
2,804✔
86
        // Where inside the file the read buffer starts
2,804✔
87
        this.readBufferPos = -1;
2,804✔
88
        this.readBufferLength = 0;
2,804✔
89

2,804✔
90
        this.headerSize = 0;
2,804✔
91
        this.size = this.readFileSize();
2,804✔
92
        if (this.size <= 0) {
2,820✔
93
            this.close();
1,200✔
94
            return false;
1,200✔
95
        }
1,200✔
96

1,604✔
97
        this.size -= this.readMetadata();
1,604✔
98

1,604✔
99
        return true;
1,604✔
100
    }
2,820✔
101

4✔
102
    /**
4✔
103
     * Read the partition metadata from the file.
4✔
104
     *
4✔
105
     * @private
4✔
106
     * @returns {number} The size of the metadata header.
4✔
107
     * @throws {Error} if the file header magic value is invalid.
4✔
108
     * @throws {Error} if the metadata size in the header is invalid.
4✔
109
     */
4✔
110
    readMetadata() {
4✔
111
        assert(this.size >= 16, `Invalid file.`);
1,604✔
112

1,604✔
113
        const headerBuffer = Buffer.allocUnsafe(8 + 4);
1,604✔
114
        fs.readSync(this.fd, headerBuffer, 0, 8 + 4, 0);
1,604✔
115
        const headerMagic = headerBuffer.toString('utf8', 0, 8);
1,604✔
116

1,604✔
117
        assert(headerMagic.substr(0, 6) === HEADER_MAGIC.substr(0, 6), `Invalid file header in partition ${this.name}.`);
1,604✔
118

1,604✔
119
        this.header = headerMagic;
1,604✔
120
        assert(headerMagic === HEADER_MAGIC, `Invalid file version. The partition ${this.name} was created with a different library version (${headerMagic.substr(6)}).`);
1,604✔
121

1,604✔
122
        const metadataSize = headerBuffer.readUInt32BE(8);
1,604✔
123
        assert(metadataSize > 2 && metadataSize <= 4096, 'Invalid metadata size.');
1,604✔
124

1,604✔
125
        const metadataBuffer = Buffer.allocUnsafe(metadataSize - 1);
1,604✔
126
        metadataBuffer.fill(" ");
1,604✔
127
        fs.readSync(this.fd, metadataBuffer, 0, metadataSize - 1, 8 + 4);
1,604✔
128
        const metadata = metadataBuffer.toString('utf8').trim();
1,604✔
129
        try {
1,604✔
130
            this.metadata = JSON.parse(metadata);
1,604✔
131
            this.metadata.epoch = this.metadata.epoch /* istanbul ignore next */|| NES_EPOCH.getTime();
1,604!
132
        } catch (e) {
1,604!
133
            throw new Error('Invalid metadata.');
×
UNCOV
134
        }
×
135
        this.headerSize = 8 + 4 + metadataSize;
1,596✔
136
        return this.headerSize;
1,596✔
137
    }
1,604✔
138

4✔
139
    /**
4✔
140
     * Get the storage size for a document of a given size.
4✔
141
     *
4✔
142
     * @param {number} dataSize The actual data size of the document.
4✔
143
     * @returns {number} The size of the data including header, padded to 16 bytes alignment and ended with a line break.
4✔
144
     */
4✔
145
    documentWriteSize(dataSize) {
4✔
146
        const padSize = alignTo(dataSize + DOCUMENT_FOOTER_SIZE, DOCUMENT_ALIGNMENT);
26,372✔
147
        return DOCUMENT_HEADER_SIZE + dataSize + padSize + DOCUMENT_FOOTER_SIZE;
26,372✔
148
    }
26,372✔
149

4✔
150
    /**
4✔
151
     * @protected
4✔
152
     * @returns {number} The file size not including the file header.
4✔
153
     */
4✔
154
    readFileSize() {
4✔
155
        const stat = fs.statSync(this.fileName);
2,816✔
156
        return stat.size - this.headerSize;
2,816✔
157
    }
2,816✔
158

4✔
159
    /**
4✔
160
     * Close the partition and frees up all resources.
4✔
161
     *
4✔
162
     * @api
4✔
163
     * @returns void
4✔
164
     */
4✔
165
    close() {
4✔
166
        if (this.fd) {
3,372✔
167
            fs.closeSync(this.fd);
2,804✔
168
            this.fd = null;
2,804✔
169
        }
2,804✔
170
        if (this.readBuffer) {
3,372✔
171
            this.readBuffer = null;
2,804✔
172
            this.readBufferPos = -1;
2,804✔
173
            this.readBufferLength = 0;
2,804✔
174
        }
2,804✔
175
    }
3,372✔
176

4✔
177
    /**
4✔
178
     * Fill the internal read buffer starting from the given position.
4✔
179
     *
4✔
180
     * @private
4✔
181
     * @param {number} [from] The file position to start filling the read buffer from. Default 0.
4✔
182
     */
4✔
183
    fillBuffer(from = 0) {
4✔
184
        this.readBufferLength = fs.readSync(this.fd, this.readBuffer, 0, this.readBuffer.byteLength, this.headerSize + from);
984✔
185
        this.readBufferPos = from;
984✔
186
    }
984✔
187

4✔
188
    /**
4✔
189
     * @private
4✔
190
     * @param {Buffer} buffer The buffer to read the data length from.
4✔
191
     * @param {number} offset The position inside the buffer to start reading from.
4✔
192
     * @param {number} position The file position to start reading from.
4✔
193
     * @param {number} [size] The expected byte size of the document at the given position.
4✔
194
     * @returns {{ dataSize: number, sequenceNumber: number, time64: number }} The metadata fields of the document
4✔
195
     * @throws {Error} if the storage entry at the given position is corrupted.
4✔
196
     * @throws {InvalidDataSizeError} if the document size at the given position does not match the provided size.
4✔
197
     * @throws {CorruptFileError} if the document at the given position can not be read completely.
4✔
198
     */
4✔
199
    readDocumentHeader(buffer, offset, position, size) {
4✔
200
        const dataSize = buffer.readUInt32BE(offset + 0);
10,540✔
201
        assert(dataSize > 0 && dataSize <= 64 * 1024 * 1024, `Error reading document size from ${position}, got ${dataSize}.`);
10,540✔
202

10,540✔
203
        if (size && dataSize !== size) {
10,540✔
204
            throw new InvalidDataSizeError(`Invalid document size ${dataSize} at position ${position}, expected ${size}.`);
8✔
205
        }
8✔
206

10,532✔
207
        const sequenceNumber = buffer.readUInt32BE(offset + 4);
10,532✔
208
        const time64 = buffer.readDoubleBE(offset + 8);
10,532✔
209
        return ({ dataSize, sequenceNumber, time64 });
10,532✔
210
    }
10,540✔
211

4✔
212
    /**
4✔
213
     * Prepare the read buffer for reading from the specified position.
4✔
214
     *
4✔
215
     * @protected
4✔
216
     * @param {number} position The position in the file to prepare the read buffer for reading from.
4✔
217
     * @returns {{ buffer: Buffer|null, cursor: number, length: number }} A reader object with properties `buffer`, `cursor` and `length`.
4✔
218
     */
4✔
219
    prepareReadBuffer(position) {
4✔
220
        if (position + DOCUMENT_HEADER_SIZE >= this.size) {
7,136!
221
            return ({ buffer: null, cursor: 0, length: 0 });
×
UNCOV
222
        }
×
223
        let bufferCursor = position - this.readBufferPos;
7,136✔
224
        if (this.readBufferPos < 0 || bufferCursor < 0 || bufferCursor + DOCUMENT_HEADER_SIZE + DOCUMENT_ALIGNMENT > this.readBufferLength) {
7,136✔
225
            this.fillBuffer(position);
668✔
226
            bufferCursor = 0;
668✔
227
        }
668✔
228
        return ({ buffer: this.readBuffer, cursor: bufferCursor, length: this.readBufferLength });
7,136✔
229
    }
7,136✔
230

4✔
231
    /**
4✔
232
     * Prepare the read buffer for reading *before* the specified position. Don't try to reader *after* the returned cursor.
4✔
233
     *
4✔
234
     * @protected
4✔
235
     * @param {number} position The position in the file to prepare the read buffer for reading before.
4✔
236
     * @returns {{ buffer: Buffer|null, cursor: number, length: number }} A reader object with properties `buffer`, `cursor` and `length`.
4✔
237
     */
4✔
238
    prepareReadBufferBackwards(position) {
4✔
239
        if (position < 0) {
2,608!
240
            return ({ buffer: null, cursor: 0, length: 0 });
×
UNCOV
241
        }
×
242
        let bufferCursor = position - this.readBufferPos;
2,608✔
243
        if (this.readBufferPos < 0 || (this.readBufferPos > 0 && bufferCursor < DOCUMENT_FOOTER_SIZE) || bufferCursor > this.readBufferLength) {
2,608✔
244
            this.fillBuffer(Math.max(position - this.readBuffer.byteLength, 0));
316✔
245
            bufferCursor = position - this.readBufferPos;
316✔
246
        }
316✔
247
        return ({ buffer: this.readBuffer, cursor: bufferCursor, length: this.readBufferLength });
2,608✔
248
    }
2,608✔
249

4✔
250
    /**
4✔
251
     * Read the data from the given position.
4✔
252
     *
4✔
253
     * @api
4✔
254
     * @param {number} position The file position to read from.
4✔
255
     * @param {number} [size] The expected byte size of the document at the given position.
4✔
256
     * @param {object|null} [headerOut] Optional object to populate with the document header fields
4✔
257
     *   (`dataSize`, `sequenceNumber`, `time64`). Pass an existing object to avoid extra allocation.
4✔
258
     * @returns {string|boolean} The data stored at the given position or false if no data could be read.
4✔
259
     * @throws {Error} if the storage entry at the given position is corrupted.
4✔
260
     * @throws {InvalidDataSizeError} if the document size at the given position does not match the provided size.
4✔
261
     * @throws {CorruptFileError} if the document at the given position can not be read completely.
4✔
262
     */
4✔
263
    readFrom(position, size = 0, headerOut = null) {
4✔
264
        assert(this.fd, 'Partition is not opened.');
9,924✔
265
        assert((position % DOCUMENT_ALIGNMENT) === 0, `Invalid read position ${position}. Needs to be a multiple of ${DOCUMENT_ALIGNMENT}.`);
9,924✔
266

9,924✔
267
        const reader = this.prepareReadBuffer(position);
9,924✔
268
        if (reader.length < size + DOCUMENT_HEADER_SIZE) {
9,924✔
269
            return false;
132✔
270
        }
132✔
271

9,780✔
272
        let dataPosition = reader.cursor + DOCUMENT_HEADER_SIZE;
9,780✔
273
        const { dataSize, sequenceNumber, time64 } = this.readDocumentHeader(reader.buffer, reader.cursor, position, size);
9,780✔
274
        if (headerOut !== null) {
9,924✔
275
            headerOut.dataSize = dataSize;
876✔
276
            headerOut.sequenceNumber = sequenceNumber;
876✔
277
            headerOut.time64 = time64;
876✔
278
        }
876✔
279

9,772✔
280
        // TODO: This should only be checked on opening
9,772✔
281
        const writeSize = this.documentWriteSize(dataSize);
9,772✔
282
        if (position + writeSize > this.size) {
9,924✔
283
            throw new CorruptFileError(`Invalid document at position ${position}. This may be caused by an unfinished write.`);
24✔
284
        }
24✔
285

9,748✔
286
        if (dataSize + DOCUMENT_HEADER_SIZE > reader.buffer.byteLength) {
9,924✔
287
            //console.log('sync read for large document size', dataLength, 'at position', position);
4✔
288
            const tempReadBuffer = Buffer.allocUnsafe(dataSize);
4✔
289
            fs.readSync(this.fd, tempReadBuffer, 0, dataSize, this.headerSize + position + DOCUMENT_HEADER_SIZE);
4✔
290
            return tempReadBuffer.toString('utf8');
4✔
291
        }
4✔
292

9,744✔
293
        if (reader.cursor > 0 && dataPosition + dataSize > reader.length) {
9,924!
294
            this.fillBuffer(position);
×
295
            dataPosition = DOCUMENT_HEADER_SIZE;
×
UNCOV
296
        }
×
297

9,744✔
298
        return reader.buffer.toString('utf8', dataPosition, dataPosition + dataSize);
9,744✔
299
    }
9,924✔
300

4✔
301
    /**
4✔
302
     * Find the start position of the document that precedes the given position.
4✔
303
     *
4✔
304
     * @protected
4✔
305
     * @param {number} position The file position to read backwards from.
4✔
306
     * @returns {number|boolean} The start position of the first document before the given position or false if no header could be found.
4✔
307
     */
4✔
308
    findDocumentPositionBefore(position) {
4✔
309
        assert(this.fd, 'Partition is not opened.');
1,576✔
310
        position -= (position % DOCUMENT_ALIGNMENT);
1,576✔
311
        if (position <= 0) {
1,576✔
312
            return false;
152✔
313
        }
152✔
314

1,424✔
315
        const separatorSize = DOCUMENT_SEPARATOR.length;
1,424✔
316
        // Optimization if we are at an exact document boundary, where we can just read the document size
1,424✔
317
        let reader = this.prepareReadBufferBackwards(position);
1,424✔
318
        const block = reader.buffer.toString('ascii', reader.cursor - separatorSize, reader.cursor);
1,424✔
319
        if (block === DOCUMENT_SEPARATOR) {
1,576✔
320
            const dataSize = reader.buffer.readUInt32BE(reader.cursor - separatorSize - 4);
1,020✔
321
            return position - this.documentWriteSize(dataSize);
1,020✔
322
        }
1,020✔
323

404✔
324
        do {
1,576✔
325
            reader = this.prepareReadBufferBackwards(position - separatorSize);
420✔
326

420✔
327
            const bufferSeparatorPosition = reader.buffer.lastIndexOf(DOCUMENT_SEPARATOR, reader.cursor - separatorSize, 'ascii');
420✔
328
            if (bufferSeparatorPosition >= 0) {
420✔
329
                position = this.readBufferPos + bufferSeparatorPosition + separatorSize;
292✔
330
                break;
292✔
331
            }
292✔
332
            position -= this.readBufferLength;
128✔
333
        } while (position > 0);
1,576✔
334
        return Math.max(0, position);
404✔
335
    }
1,576✔
336

4✔
337
    /**
4✔
338
     * Find the document that starts immediately before `position`, fill the read buffer
4✔
339
     * centered around that document's start, and return its file position and parsed header.
4✔
340
     * The buffer is centered by calling `prepareReadBufferBackwards` with a position
4✔
341
     * half a buffer-length ahead of the document start, clamped to file size, so the
4✔
342
     * document start lands near the middle of the buffer.
4✔
343
     *
4✔
344
     * @private
4✔
345
     * @param {number} position The file position to search before.
4✔
346
     * @returns {{ header: {dataSize: number, sequenceNumber: number, time64: number}, position: number }|null}
4✔
347
     *   The document header and file position, or null if no document could be found.
4✔
348
     */
4✔
349
    readDocumentBefore(position) {
4✔
350
        const docPos = this.findDocumentPositionBefore(position);
876✔
351
        /* istanbul ignore if */
876✔
352
        if (docPos === false || docPos < 0) return null;
876✔
353
        const reader = this.prepareReadBufferBackwards(Math.min(docPos + (this.readBuffer.byteLength >> 1), this.size));
756✔
354
        /* istanbul ignore if */
756✔
355
        if (!reader.buffer) return null;
876!
356
        const cursor = docPos - this.readBufferPos;
756✔
357
        /* istanbul ignore if */
756✔
358
        if (cursor < 0 || cursor + DOCUMENT_HEADER_SIZE > reader.length) return null;
876✔
359
        const header = this.readDocumentHeader(reader.buffer, cursor, docPos);
732✔
360
        return { header, position: docPos };
732✔
361
    }
876✔
362

4✔
363
    /**
4✔
364
     * Read the header and file position of the last document in this partition.
4✔
365
     *
4✔
366
     * @api
4✔
367
     * @returns {{ header: {dataSize: number, sequenceNumber: number, time64: number}, position: number } | null}
4✔
368
     *   The last document's header and its file position, or null if the partition is empty or unreadable.
4✔
369
     */
4✔
370
    readLast() {
4✔
371
        if (this.size === 0) return null;
188✔
372
        return this.readDocumentBefore(this.size);
172✔
373
    }
188✔
374

4✔
375
    /**
4✔
376
     * Find the first document whose sequenceNumber is >= the given value.
4✔
377
     * Uses readLast() to short-circuit when the partition contains no such document.
4✔
378
     * Uses a binary search over file positions via readDocumentBefore() to locate the
4✔
379
     * document. The search tracks both the lower bound (position just after the last
4✔
380
     * confirmed "< sequenceNumber" doc) and the upper bound (minimum position of any
4✔
381
     * probed doc with sequenceNumber >= target). The upper bound, when available, is
4✔
382
     * the exact target document, so no further linear scan is needed.
4✔
383
     *
4✔
384
     * @api
4✔
385
     * @param {number} sequenceNumber The 0-based sequence number to search for.
4✔
386
     * @returns {{ reader: Generator<string>, headerOut: object, data: string }|null}
4✔
387
     *   The matched document with its reader and shared headerOut, or null if no such document exists.
4✔
388
     */
4✔
389
    findDocument(sequenceNumber) {
4✔
390
        const last = this.readLast();
144✔
391
        if (!last || last.header.sequenceNumber < sequenceNumber) {
144✔
392
            return null;
24✔
393
        }
24✔
394

120✔
395
        let startPosition = this.size;
120✔
396
        binarySearch(
120✔
397
            sequenceNumber,
120✔
398
            this.size,
120✔
399
            (pos) => {
120✔
400
                const doc = this.readDocumentBefore(pos);
704✔
401
                if (!doc) return sequenceNumber;
704✔
402
                if (doc.header.sequenceNumber < sequenceNumber) {
704✔
403
                    startPosition = Math.max(startPosition, doc.position + this.documentWriteSize(doc.header.dataSize));
180✔
404
                } else {
704✔
405
                    startPosition = Math.min(startPosition, doc.position);
380✔
406
                }
380✔
407
                return doc.header.sequenceNumber;
560✔
408
            }
704✔
409
        );
120✔
410

120✔
411
        const headerOut = {};
120✔
412
        const data = this.readFrom(startPosition, 0, headerOut);
120✔
413
        /* istanbul ignore if */
120✔
414
        if (data === false) {
144!
UNCOV
415
            return null;
×
UNCOV
416
        }
×
417
        headerOut.position = startPosition;
120✔
418
        return { headerOut, data };
120✔
419
    }
144✔
420

4✔
421
    /**
4✔
422
     * @api
4✔
423
     * @param {number} [after] The document position to start reading from.
4✔
424
     * @param {object|null} [headerOut] Optional object to populate with document header fields
4✔
425
     *   (`dataSize`, `sequenceNumber`, `time64`, `position`) on each yield. Pass an existing object
4✔
426
     *   to avoid extra allocation. The object is mutated in place before each yield.
4✔
427
     * @returns {Generator<string>} A generator that returns all documents in this partition.
4✔
428
     */
4✔
429
    *readAll(after = 0, headerOut = null) {
4✔
430
        let position = after < 0 ? this.size + after + 1 : after;
112✔
431
        const internalHeader = headerOut !== null ? headerOut : {};
112✔
432
        let data;
112✔
433
        while ((data = this.readFrom(position, 0, internalHeader)) !== false) {
112✔
434
            if (headerOut !== null) {
756✔
435
                headerOut.position = position;
144✔
436
            }
144✔
437
            yield data;
756✔
438
            position += this.documentWriteSize(internalHeader.dataSize);
748✔
439
        }
748✔
440
    }
112✔
441

4✔
442
    /**
4✔
443
     * @api
4✔
444
     * @param {number} [before] The document position to start reading backward from.
4✔
445
     * @returns {Generator<string>} A generator that returns all documents in this partition in reverse order.
4✔
446
     */
4✔
447
    *readAllBackwards(before = -1) {
4✔
448
        let position = before < 0 ? this.size + before + 1 : before;
24✔
449
        while ((position = this.findDocumentPositionBefore(position)) !== false) {
24✔
450
            const data = this.readFrom(position);
636✔
451
            yield data;
636✔
452
        }
636✔
453
    }
24✔
454
}
4✔
455

4✔
456
export default ReadablePartition;
4✔
457
export { CorruptFileError, InvalidDataSizeError, HEADER_MAGIC, DOCUMENT_SEPARATOR, DOCUMENT_ALIGNMENT, DOCUMENT_HEADER_SIZE, DOCUMENT_FOOTER_SIZE };
4✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc