event-storage 1.1.0 → 1.2.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 +21 -0
- package/package.json +3 -5
- package/src/Consumer.js +9 -7
- package/src/EventStore.js +165 -83
- package/src/EventStream.js +49 -11
- package/src/Index/ReadableIndex.js +7 -5
- package/src/Index/WritableIndex.js +4 -6
- package/src/IndexMatcher.js +2 -2
- package/src/JoinEventStream.js +30 -46
- package/src/Partition/ReadablePartition.js +153 -85
- package/src/Partition/WritablePartition.js +37 -28
- package/src/Storage/ReadOnlyStorage.js +3 -3
- package/src/Storage/ReadableStorage.js +73 -102
- package/src/Storage/WritableStorage.js +43 -25
- package/src/Watcher.js +1 -1
- package/src/utils/jsonUtil.js +87 -0
- package/src/utils/metadataUtil.js +247 -0
- package/src/{util.js → utils/util.js} +52 -17
- package/src/metadataUtil.js +0 -126
- /package/src/{fsUtil.js → utils/fsUtil.js} +0 -0
|
@@ -3,13 +3,14 @@ import path from 'path';
|
|
|
3
3
|
import events from 'events';
|
|
4
4
|
import Partition, { ReadOnly as ReadOnlyPartition } from '../Partition.js';
|
|
5
5
|
import Index, { ReadOnly as ReadOnlyIndex } from '../Index.js';
|
|
6
|
-
import { assert, wrapAndCheck, kWayMerge } from '../util.js';
|
|
7
|
-
import { scanForFiles } from '../fsUtil.js';
|
|
8
|
-
import { createHmac, matches, buildMetadataForMatcher } from '../metadataUtil.js';
|
|
6
|
+
import { assert, wrapAndCheck, iterate, kWayMerge } from '../utils/util.js';
|
|
7
|
+
import { scanForFiles } from '../utils/fsUtil.js';
|
|
8
|
+
import { createHmac, matches, buildMetadataForMatcher } from '../utils/metadataUtil.js';
|
|
9
9
|
import IndexMatcher from '../IndexMatcher.js';
|
|
10
10
|
import PartitionPool from '../PartitionPool.js';
|
|
11
11
|
|
|
12
12
|
const DEFAULT_READ_BUFFER_SIZE = 4 * 1024;
|
|
13
|
+
const NDJSON_NEWLINE = Buffer.from('\n');
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Default ordered list of document property paths used as discriminant keys when
|
|
@@ -26,18 +27,6 @@ const DEFAULT_MATCHER_PROPERTIES = ['stream', 'payload.type'];
|
|
|
26
27
|
*/
|
|
27
28
|
const DEFAULT_MAX_OPEN_PARTITIONS = 1024;
|
|
28
29
|
|
|
29
|
-
/**
|
|
30
|
-
* Reverses the items of an iterable
|
|
31
|
-
* @param {Generator|Iterable} iterator
|
|
32
|
-
* @returns {Generator<*>}
|
|
33
|
-
*/
|
|
34
|
-
function *reverse(iterator) {
|
|
35
|
-
const items = Array.from(iterator);
|
|
36
|
-
for (let i = items.length - 1; i >= 0; i--) {
|
|
37
|
-
yield items[i];
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
30
|
/**
|
|
42
31
|
* @typedef {object|function(object):boolean} Matcher
|
|
43
32
|
*/
|
|
@@ -284,16 +273,19 @@ class ReadableStorage extends events.EventEmitter {
|
|
|
284
273
|
* @param {number} partitionId The partition to read from.
|
|
285
274
|
* @param {number} position The file position to read from.
|
|
286
275
|
* @param {number} [size] The expected byte size of the document at the given position.
|
|
287
|
-
* @
|
|
276
|
+
* @param {boolean} [raw] Whether to return raw buffers instead of deserialized objects. Default false.
|
|
277
|
+
* @param {boolean} [backwardsHint] If set to true, will optimize buffering for backwards reading.
|
|
278
|
+
* @returns {object|{ buffer: Buffer, time64: number, sequenceNumber: number }} The document stored at the given position.
|
|
288
279
|
* @throws {Error} if the document at the given position can not be deserialized.
|
|
289
280
|
*/
|
|
290
|
-
readFrom(partitionId, position, size) {
|
|
281
|
+
readFrom(partitionId, position, size, raw = false, backwardsHint = false) {
|
|
291
282
|
const partition = this.getPartition(partitionId);
|
|
292
283
|
if (this.listenerCount('preRead') > 0) {
|
|
293
284
|
this.emit('preRead', position, partition.metadata);
|
|
294
285
|
}
|
|
295
|
-
const
|
|
296
|
-
|
|
286
|
+
const headerOut = {};
|
|
287
|
+
const buffer = partition.readFrom(position, size, headerOut, backwardsHint);
|
|
288
|
+
return raw ? { buffer, time64: headerOut.time64, sequenceNumber: headerOut.sequenceNumber } : this.serializer.deserialize(buffer.toString('utf8'));
|
|
297
289
|
}
|
|
298
290
|
|
|
299
291
|
/**
|
|
@@ -306,10 +298,7 @@ class ReadableStorage extends events.EventEmitter {
|
|
|
306
298
|
*/
|
|
307
299
|
read(number, index) {
|
|
308
300
|
index = index || this.index;
|
|
309
|
-
|
|
310
|
-
if (!index.isOpen()) {
|
|
311
|
-
index.open();
|
|
312
|
-
}
|
|
301
|
+
index.open();
|
|
313
302
|
|
|
314
303
|
const entry = index.get(number);
|
|
315
304
|
if (entry === false) {
|
|
@@ -329,30 +318,22 @@ class ReadableStorage extends events.EventEmitter {
|
|
|
329
318
|
* @param {ReadableIndex|false} [index] The index to use for finding the documents in the range.
|
|
330
319
|
* Pass `false` to skip the global index and iterate all partitions directly in sequenceNumber order
|
|
331
320
|
* (useful when the global index is unavailable or corrupted).
|
|
321
|
+
* @param {boolean} [raw] Whether to return raw buffers instead of deserialized objects. Default false.
|
|
332
322
|
* @returns {Generator<object>} A generator that will read each document in the range one by one.
|
|
333
323
|
*/
|
|
334
|
-
*readRange(from, until = -1, index = null) {
|
|
335
|
-
|
|
336
|
-
if (
|
|
337
|
-
|
|
324
|
+
*readRange(from, until = -1, index = null, raw = false) {
|
|
325
|
+
let length = Number.MAX_SAFE_INTEGER;
|
|
326
|
+
if (index !== false) {
|
|
327
|
+
index = index || this.index;
|
|
328
|
+
index.open();
|
|
329
|
+
length = index.length;
|
|
338
330
|
}
|
|
339
331
|
|
|
340
|
-
const readFrom = wrapAndCheck(from,
|
|
341
|
-
const readUntil = wrapAndCheck(until,
|
|
332
|
+
const readFrom = wrapAndCheck(from, length);
|
|
333
|
+
const readUntil = wrapAndCheck(until, length);
|
|
342
334
|
assert(readFrom > 0 && readUntil > 0, `Range scan error for range ${from} - ${until}.`);
|
|
343
335
|
|
|
344
|
-
|
|
345
|
-
const batchSize = 10;
|
|
346
|
-
let batchUntil = readFrom;
|
|
347
|
-
while (batchUntil >= readUntil) {
|
|
348
|
-
const batchFrom = Math.max(readUntil, batchUntil - batchSize);
|
|
349
|
-
yield* reverse(this.iterateRange(batchFrom, batchUntil, index));
|
|
350
|
-
batchUntil = batchFrom - 1;
|
|
351
|
-
}
|
|
352
|
-
return undefined;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
yield* this.iterateRange(readFrom, readUntil, index);
|
|
336
|
+
yield* this.iterateRange(readFrom, readUntil, index, raw);
|
|
356
337
|
}
|
|
357
338
|
|
|
358
339
|
/**
|
|
@@ -362,23 +343,25 @@ class ReadableStorage extends events.EventEmitter {
|
|
|
362
343
|
* @param {number} from
|
|
363
344
|
* @param {number} until
|
|
364
345
|
* @param {ReadableIndex|false|null} index
|
|
346
|
+
* @param {boolean} [raw] Whether to return raw buffers instead of deserialized objects. Default false.
|
|
365
347
|
* @returns {Generator<object>}
|
|
366
348
|
*/
|
|
367
|
-
*iterateRange(from, until, index) {
|
|
349
|
+
*iterateRange(from, until, index, raw = false) {
|
|
368
350
|
if (index === false) {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
for (const entry of this.iterateDocumentsNoIndex(from - 1, until - 1)) {
|
|
372
|
-
yield entry.document;
|
|
351
|
+
for (const { document } of this.iterateDocumentsNoIndex(from - 1, until - 1)) {
|
|
352
|
+
yield document;
|
|
373
353
|
}
|
|
374
354
|
return;
|
|
375
355
|
}
|
|
376
356
|
|
|
377
357
|
const idx = index || this.index;
|
|
378
|
-
const
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
358
|
+
const forwards = from <= until;
|
|
359
|
+
const lo = Math.min(from, until);
|
|
360
|
+
const hi = Math.max(from, until);
|
|
361
|
+
const entries = idx.range(lo, hi);
|
|
362
|
+
if (!entries) return;
|
|
363
|
+
for (const entry of iterate(entries, forwards)) {
|
|
364
|
+
yield this.readFrom(entry.partition, entry.position, entry.size, raw, !forwards);
|
|
382
365
|
}
|
|
383
366
|
}
|
|
384
367
|
|
|
@@ -448,62 +431,50 @@ class ReadableStorage extends events.EventEmitter {
|
|
|
448
431
|
}
|
|
449
432
|
}
|
|
450
433
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
stream => items.push({
|
|
489
|
-
document: this.serializer.deserialize(stream.data),
|
|
490
|
-
sequenceNumber: stream.headerOut.sequenceNumber,
|
|
491
|
-
partitionName: stream.partitionName,
|
|
492
|
-
position: stream.headerOut.position,
|
|
493
|
-
size: stream.headerOut.dataSize,
|
|
494
|
-
partition: stream.partition,
|
|
495
|
-
})
|
|
496
|
-
);
|
|
497
|
-
|
|
498
|
-
yield* items;
|
|
499
|
-
}
|
|
434
|
+
/**
|
|
435
|
+
* Build the standard document result entry from a readRange yield.
|
|
436
|
+
* @private
|
|
437
|
+
* @param {{ data: Buffer, entry: { number: number, position: number, size: number, partition: number } }} [readItem]
|
|
438
|
+
*/
|
|
439
|
+
buildDocumentEntry(readItem) {
|
|
440
|
+
return {
|
|
441
|
+
document: this.serializer.deserialize(readItem.data.toString('utf8')),
|
|
442
|
+
// Replicate the index entry structure here, so iteration can be used easily to reindex
|
|
443
|
+
entry: readItem.entry
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Iterate documents across all partitions in sequenceNumber order using a k-way merge.
|
|
449
|
+
* Opens any closed partition automatically.
|
|
450
|
+
*
|
|
451
|
+
* @protected
|
|
452
|
+
* @param {number} [from=0] The 0-based sequenceNumber to start from (inclusive).
|
|
453
|
+
* @param {number} [until=Number.MAX_SAFE_INTEGER] The 0-based sequenceNumber to read until (inclusive).
|
|
454
|
+
* @returns {Generator<{document: object, entry: { sequenceNumber: number, position: number, size: number, partition: number }}>}
|
|
455
|
+
*/
|
|
456
|
+
*iterateDocumentsNoIndex(from = 0, until = Number.MAX_SAFE_INTEGER) {
|
|
457
|
+
const forwards = from <= until;
|
|
458
|
+
const partitions = [];
|
|
459
|
+
this.forEachPartition(partition => {
|
|
460
|
+
partition.open();
|
|
461
|
+
partitions.push(partition.readRange(from, until));
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
yield* kWayMerge(
|
|
465
|
+
partitions,
|
|
466
|
+
item => item.entry.number,
|
|
467
|
+
forwards,
|
|
468
|
+
item => this.buildDocumentEntry(item)
|
|
469
|
+
);
|
|
470
|
+
}
|
|
500
471
|
|
|
501
472
|
/**
|
|
502
473
|
* Helper method to iterate over all documents, invoking a callback for each one.
|
|
503
474
|
* Pass `noIndex = true` to iterate all partitions directly in sequenceNumber order
|
|
504
475
|
* (useful when the global index is unavailable or corrupted).
|
|
505
476
|
* When `noIndex` is false the second callback argument is the raw index `EntryInterface`.
|
|
506
|
-
* When `noIndex` is true the second callback argument has `{ partition, position, size, sequenceNumber
|
|
477
|
+
* When `noIndex` is true the second callback argument has `{ partition, position, size, sequenceNumber }`.
|
|
507
478
|
*
|
|
508
479
|
* @protected
|
|
509
480
|
* @param {function(object, object): void} iterationHandler
|
|
@@ -516,8 +487,8 @@ class ReadableStorage extends events.EventEmitter {
|
|
|
516
487
|
}
|
|
517
488
|
|
|
518
489
|
if (noIndex) {
|
|
519
|
-
for (const { document,
|
|
520
|
-
iterationHandler(document,
|
|
490
|
+
for (const { document, entry } of this.iterateDocumentsNoIndex()) {
|
|
491
|
+
iterationHandler(document, entry);
|
|
521
492
|
}
|
|
522
493
|
return;
|
|
523
494
|
}
|
|
@@ -3,9 +3,9 @@ import path from 'path';
|
|
|
3
3
|
import WritablePartition from '../Partition/WritablePartition.js';
|
|
4
4
|
import WritableIndex, { Entry as WritableIndexEntry } from '../Index/WritableIndex.js';
|
|
5
5
|
import ReadableStorage from './ReadableStorage.js';
|
|
6
|
-
import { assert } from '../util.js';
|
|
7
|
-
import { ensureDirectory } from '../fsUtil.js';
|
|
8
|
-
import { matches, buildMetadataForMatcher, buildMatcherFromMetadata } from '../metadataUtil.js';
|
|
6
|
+
import { assert } from '../utils/util.js';
|
|
7
|
+
import { ensureDirectory } from '../utils/fsUtil.js';
|
|
8
|
+
import { matches, buildMetadataForMatcher, buildMatcherFromMetadata } from '../utils/metadataUtil.js';
|
|
9
9
|
|
|
10
10
|
const DEFAULT_WRITE_BUFFER_SIZE = 16 * 1024;
|
|
11
11
|
|
|
@@ -64,6 +64,7 @@ class WritableStorage extends ReadableStorage {
|
|
|
64
64
|
this.lockFile = path.resolve(this.dataDirectory, this.storageFile + '.lock');
|
|
65
65
|
this._lockMode = config.lock;
|
|
66
66
|
this.partitioner = config.partitioner;
|
|
67
|
+
this.partitionIds = {};
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
/**
|
|
@@ -204,8 +205,8 @@ class WritableStorage extends ReadableStorage {
|
|
|
204
205
|
|
|
205
206
|
// Scan partitions in sequence-number order and rebuild index entries.
|
|
206
207
|
// iterateDocumentsNoIndex opens any closed partitions automatically.
|
|
207
|
-
for (const { document,
|
|
208
|
-
const newEntry = new WritableIndexEntry(this.index.length + 1, position, size, partition);
|
|
208
|
+
for (const { document, entry } of this.iterateDocumentsNoIndex(fromSequenceNumber)) {
|
|
209
|
+
const newEntry = new WritableIndexEntry(this.index.length + 1, entry.position, entry.size, entry.partition);
|
|
209
210
|
this.index.add(newEntry);
|
|
210
211
|
|
|
211
212
|
this.forEachWritableSecondaryIndex((secIndex) => {
|
|
@@ -231,9 +232,8 @@ class WritableStorage extends ReadableStorage {
|
|
|
231
232
|
this.locked = true;
|
|
232
233
|
} catch (e) {
|
|
233
234
|
/* istanbul ignore if */
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
}
|
|
235
|
+
assert(e.code === 'EEXIST', `Error creating lock for storage ${this.storageFile}: ` + e.message)
|
|
236
|
+
|
|
237
237
|
throw new StorageLockedError(`Storage ${this.storageFile} is locked by another process`);
|
|
238
238
|
}
|
|
239
239
|
return true;
|
|
@@ -311,6 +311,36 @@ class WritableStorage extends ReadableStorage {
|
|
|
311
311
|
this.on('preCommit', hook);
|
|
312
312
|
}
|
|
313
313
|
|
|
314
|
+
getPartitionIdForName(partitionShortName, partitionName) {
|
|
315
|
+
const partitionId = this.partitionIds[partitionShortName] ?? WritablePartition.idFor(partitionName);
|
|
316
|
+
this.partitionIds[partitionShortName] = partitionId;
|
|
317
|
+
return partitionId;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
buildPartitionConfig(partitionShortName) {
|
|
321
|
+
if (typeof this.partitionConfig.metadata !== 'function') {
|
|
322
|
+
return this.partitionConfig;
|
|
323
|
+
}
|
|
324
|
+
return { ...this.partitionConfig, metadata: this.partitionConfig.metadata(partitionShortName) };
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
ensurePartitionDirectory(partitionName) {
|
|
328
|
+
if (!partitionName.includes('/')) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
ensureDirectory(path.join(this.dataDirectory, path.dirname(partitionName)));
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
createNamedPartition(partitionId, partitionName, partitionShortName) {
|
|
335
|
+
if (this.partitions.has(partitionId)) {
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
const partitionConfig = this.buildPartitionConfig(partitionShortName);
|
|
339
|
+
this.ensurePartitionDirectory(partitionName);
|
|
340
|
+
this.partitions.add(partitionId, this.createPartition(partitionName, partitionConfig));
|
|
341
|
+
this.emit('partition-created', partitionId);
|
|
342
|
+
}
|
|
343
|
+
|
|
314
344
|
/**
|
|
315
345
|
* Get a partition either by name or by id.
|
|
316
346
|
* If a partition with the given name does not exist, a new one will be created.
|
|
@@ -327,17 +357,8 @@ class WritableStorage extends ReadableStorage {
|
|
|
327
357
|
if (typeof partitionIdentifier === 'string') {
|
|
328
358
|
const partitionShortName = partitionIdentifier;
|
|
329
359
|
const partitionName = this.storageFile + (partitionIdentifier.length ? '.' + partitionIdentifier : '');
|
|
330
|
-
partitionIdentifier =
|
|
331
|
-
|
|
332
|
-
const partitionConfig = typeof this.partitionConfig.metadata === 'function'
|
|
333
|
-
? { ...this.partitionConfig, metadata: this.partitionConfig.metadata(partitionShortName) }
|
|
334
|
-
: this.partitionConfig;
|
|
335
|
-
if (partitionName.includes('/')) {
|
|
336
|
-
ensureDirectory(path.join(this.dataDirectory, path.dirname(partitionName)));
|
|
337
|
-
}
|
|
338
|
-
this.partitions.add(partitionIdentifier, this.createPartition(partitionName, partitionConfig));
|
|
339
|
-
this.emit('partition-created', partitionIdentifier);
|
|
340
|
-
}
|
|
360
|
+
partitionIdentifier = this.getPartitionIdForName(partitionShortName, partitionName);
|
|
361
|
+
this.createNamedPartition(partitionIdentifier, partitionName, partitionShortName);
|
|
341
362
|
}
|
|
342
363
|
return super.getPartition(partitionIdentifier);
|
|
343
364
|
}
|
|
@@ -354,18 +375,15 @@ class WritableStorage extends ReadableStorage {
|
|
|
354
375
|
|
|
355
376
|
const partitionName = this.partitioner(document, this.index.length + 1);
|
|
356
377
|
const partition = this.getPartition(partitionName);
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
}
|
|
378
|
+
this.emit('preCommit', document, partition.metadata);
|
|
379
|
+
|
|
360
380
|
const position = partition.write(data, this.length, callback);
|
|
361
381
|
|
|
362
382
|
assert(position !== false, 'Error writing document.');
|
|
363
383
|
|
|
364
384
|
const indexEntry = this.addIndex(partition.id, position, dataSize, document);
|
|
365
385
|
this.forEachSecondaryIndex((index, name) => {
|
|
366
|
-
|
|
367
|
-
index.open();
|
|
368
|
-
}
|
|
386
|
+
index.open();
|
|
369
387
|
index.add(indexEntry);
|
|
370
388
|
this.emit('index-add', name, index.length, document);
|
|
371
389
|
}, document);
|
package/src/Watcher.js
CHANGED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
const BYTE_QUOTE = 0x22;
|
|
2
|
+
const BYTE_ESCAPE = 0x5c;
|
|
3
|
+
const BYTE_OPEN_OBJECT = 0x7b;
|
|
4
|
+
const BYTE_CLOSE_OBJECT = 0x7d;
|
|
5
|
+
const BYTE_OPEN_ARRAY = 0x5b;
|
|
6
|
+
const BYTE_CLOSE_ARRAY = 0x5d;
|
|
7
|
+
const BYTE_COMMA = 0x2c;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Find the position of `pattern` within `buffer` at depth 0 (the top-level object), starting
|
|
11
|
+
* from `startOffset`. It scans character-by-character tracking JSON nesting depth and string
|
|
12
|
+
* quoting. If `matchPosition` arrives at depth > 0 it means the pattern is inside a nested
|
|
13
|
+
* object/array, so the scan continues searching for the next candidate at depth 0. Returns -1
|
|
14
|
+
* when no such position exists before the end of the buffer or when a closing brace reduces depth
|
|
15
|
+
* below zero (the top-level object has ended).
|
|
16
|
+
*/
|
|
17
|
+
function indexOfSameLevel(buffer, pattern, startOffset = 0, matchPosition) {
|
|
18
|
+
/* c8 ignore start */
|
|
19
|
+
// Defensive fallback: public call path precomputes an initial candidate in preCheck.
|
|
20
|
+
if (matchPosition === undefined) {
|
|
21
|
+
matchPosition = buffer.indexOf(pattern, startOffset);
|
|
22
|
+
}
|
|
23
|
+
if (matchPosition === -1) {
|
|
24
|
+
return -1;
|
|
25
|
+
}
|
|
26
|
+
/* c8 ignore stop */
|
|
27
|
+
|
|
28
|
+
let depth = 0;
|
|
29
|
+
let inString = false;
|
|
30
|
+
let i = startOffset;
|
|
31
|
+
|
|
32
|
+
while (i < buffer.length) {
|
|
33
|
+
if (inString) {
|
|
34
|
+
if (buffer[i] === BYTE_ESCAPE) { // '\\'
|
|
35
|
+
i += 2;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (buffer[i] === BYTE_QUOTE) { // '"'
|
|
39
|
+
inString = false;
|
|
40
|
+
}
|
|
41
|
+
i++;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const ch = buffer[i];
|
|
46
|
+
if (ch === BYTE_OPEN_OBJECT || ch === BYTE_OPEN_ARRAY) { // '{' or '['
|
|
47
|
+
depth++;
|
|
48
|
+
i++;
|
|
49
|
+
continue;
|
|
50
|
+
} else if (ch === BYTE_CLOSE_OBJECT || ch === BYTE_CLOSE_ARRAY) { // '}' or ']'
|
|
51
|
+
depth--;
|
|
52
|
+
|
|
53
|
+
if (depth < 0) {
|
|
54
|
+
return -1;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
i++;
|
|
58
|
+
continue;
|
|
59
|
+
} else if (ch === BYTE_QUOTE) { // '"'
|
|
60
|
+
inString = true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (i >= matchPosition) {
|
|
64
|
+
if (i === matchPosition && ch === BYTE_QUOTE && depth === 0) { // '"'
|
|
65
|
+
const end = i + pattern.length;
|
|
66
|
+
if (pattern[pattern.length - 1] === BYTE_OPEN_OBJECT) { // '{'
|
|
67
|
+
return i;
|
|
68
|
+
}
|
|
69
|
+
if (buffer[end] === BYTE_COMMA || buffer[end] === BYTE_CLOSE_OBJECT || buffer[end] === BYTE_CLOSE_ARRAY) { // ',' or '}' or ']'
|
|
70
|
+
return i;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
matchPosition = buffer.indexOf(pattern, matchPosition + 1);
|
|
75
|
+
if (matchPosition < 0) {
|
|
76
|
+
return -1;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
i++;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* c8 ignore next */
|
|
84
|
+
return -1;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export { BYTE_OPEN_OBJECT, indexOfSameLevel };
|