@indigoai-us/hq-cloud 6.11.10 → 6.11.12
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/dist/bin/sync-runner.d.ts +2 -0
- package/dist/bin/sync-runner.d.ts.map +1 -1
- package/dist/bin/sync-runner.js +231 -52
- package/dist/bin/sync-runner.js.map +1 -1
- package/dist/bin/sync-runner.test.js +330 -11
- package/dist/bin/sync-runner.test.js.map +1 -1
- package/dist/cli/reindex.d.ts.map +1 -1
- package/dist/cli/reindex.js +16 -1
- package/dist/cli/reindex.js.map +1 -1
- package/dist/cli/reindex.test.js +39 -1
- package/dist/cli/reindex.test.js.map +1 -1
- package/dist/cli/rescue-classify-ordering.test.js +58 -0
- package/dist/cli/rescue-classify-ordering.test.js.map +1 -1
- package/dist/cli/rescue-core.js +229 -15
- package/dist/cli/rescue-core.js.map +1 -1
- package/dist/cli/rescue-exec-bit-preserve.test.d.ts +2 -0
- package/dist/cli/rescue-exec-bit-preserve.test.d.ts.map +1 -0
- package/dist/cli/rescue-exec-bit-preserve.test.js +169 -0
- package/dist/cli/rescue-exec-bit-preserve.test.js.map +1 -0
- package/dist/cli/share.d.ts +2 -1
- package/dist/cli/share.d.ts.map +1 -1
- package/dist/cli/share.js +100 -32
- package/dist/cli/share.js.map +1 -1
- package/dist/cli/share.test.js +30 -0
- package/dist/cli/share.test.js.map +1 -1
- package/dist/cli/sync.d.ts +28 -1
- package/dist/cli/sync.d.ts.map +1 -1
- package/dist/cli/sync.js +188 -59
- package/dist/cli/sync.js.map +1 -1
- package/dist/cli/sync.test.js +487 -1
- package/dist/cli/sync.test.js.map +1 -1
- package/dist/cognito-auth.d.ts.map +1 -1
- package/dist/cognito-auth.js +55 -10
- package/dist/cognito-auth.js.map +1 -1
- package/dist/cognito-auth.test.js +61 -0
- package/dist/cognito-auth.test.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/journal.d.ts.map +1 -1
- package/dist/journal.js +93 -6
- package/dist/journal.js.map +1 -1
- package/dist/journal.test.js +59 -0
- package/dist/journal.test.js.map +1 -1
- package/dist/machine-auth.test.js +60 -2
- package/dist/machine-auth.test.js.map +1 -1
- package/dist/object-io.d.ts +37 -1
- package/dist/object-io.d.ts.map +1 -1
- package/dist/object-io.js +148 -29
- package/dist/object-io.js.map +1 -1
- package/dist/object-io.test.js +121 -0
- package/dist/object-io.test.js.map +1 -1
- package/dist/operation-lock.d.ts +8 -8
- package/dist/operation-lock.d.ts.map +1 -1
- package/dist/operation-lock.js +99 -32
- package/dist/operation-lock.js.map +1 -1
- package/dist/operation-lock.test.js +51 -4
- package/dist/operation-lock.test.js.map +1 -1
- package/dist/personal-vault.d.ts +8 -0
- package/dist/personal-vault.d.ts.map +1 -1
- package/dist/personal-vault.js +17 -3
- package/dist/personal-vault.js.map +1 -1
- package/dist/personal-vault.test.js +34 -0
- package/dist/personal-vault.test.js.map +1 -1
- package/dist/prefix-coalesce.d.ts +20 -9
- package/dist/prefix-coalesce.d.ts.map +1 -1
- package/dist/prefix-coalesce.js +124 -28
- package/dist/prefix-coalesce.js.map +1 -1
- package/dist/prefix-coalesce.test.js +57 -2
- package/dist/prefix-coalesce.test.js.map +1 -1
- package/dist/remote-pull.d.ts +6 -1
- package/dist/remote-pull.d.ts.map +1 -1
- package/dist/remote-pull.js +62 -13
- package/dist/remote-pull.js.map +1 -1
- package/dist/remote-pull.test.js +189 -0
- package/dist/remote-pull.test.js.map +1 -1
- package/dist/s3.d.ts +2 -0
- package/dist/s3.d.ts.map +1 -1
- package/dist/s3.js +197 -116
- package/dist/s3.js.map +1 -1
- package/dist/s3.test.js +109 -0
- package/dist/s3.test.js.map +1 -1
- package/dist/scope-shrink.d.ts +3 -2
- package/dist/scope-shrink.d.ts.map +1 -1
- package/dist/scope-shrink.js +1 -1
- package/dist/scope-shrink.js.map +1 -1
- package/dist/skill-telemetry.d.ts +1 -1
- package/dist/skill-telemetry.d.ts.map +1 -1
- package/dist/skill-telemetry.js +69 -9
- package/dist/skill-telemetry.js.map +1 -1
- package/dist/skill-telemetry.test.js +86 -0
- package/dist/skill-telemetry.test.js.map +1 -1
- package/dist/sync/event-sync.d.ts +6 -0
- package/dist/sync/event-sync.d.ts.map +1 -1
- package/dist/sync/event-sync.js +34 -1
- package/dist/sync/event-sync.js.map +1 -1
- package/dist/sync/event-sync.test.js +73 -0
- package/dist/sync/event-sync.test.js.map +1 -1
- package/dist/sync/metrics.d.ts +17 -1
- package/dist/sync/metrics.d.ts.map +1 -1
- package/dist/sync/metrics.js +32 -1
- package/dist/sync/metrics.js.map +1 -1
- package/dist/sync/metrics.test.js +74 -1
- package/dist/sync/metrics.test.js.map +1 -1
- package/dist/sync/pull-scope.d.ts.map +1 -1
- package/dist/sync/pull-scope.js +15 -7
- package/dist/sync/pull-scope.js.map +1 -1
- package/dist/sync/push-receiver.d.ts +6 -5
- package/dist/sync/push-receiver.d.ts.map +1 -1
- package/dist/sync/push-receiver.js +13 -15
- package/dist/sync/push-receiver.js.map +1 -1
- package/dist/sync/push-receiver.test.js +36 -1
- package/dist/sync/push-receiver.test.js.map +1 -1
- package/dist/telemetry.d.ts +1 -1
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +59 -6
- package/dist/telemetry.js.map +1 -1
- package/dist/telemetry.test.js +74 -0
- package/dist/telemetry.test.js.map +1 -1
- package/dist/types.d.ts +8 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/watcher.d.ts +36 -0
- package/dist/watcher.d.ts.map +1 -1
- package/dist/watcher.js +152 -30
- package/dist/watcher.js.map +1 -1
- package/dist/watcher.test.js +103 -0
- package/dist/watcher.test.js.map +1 -1
- package/package.json +1 -1
- package/src/bin/sync-runner.test.ts +396 -11
- package/src/bin/sync-runner.ts +254 -52
- package/src/cli/reindex.test.ts +47 -1
- package/src/cli/reindex.ts +17 -1
- package/src/cli/rescue-classify-ordering.test.ts +61 -0
- package/src/cli/rescue-core.ts +261 -15
- package/src/cli/rescue-exec-bit-preserve.test.ts +187 -0
- package/src/cli/share.test.ts +38 -0
- package/src/cli/share.ts +103 -34
- package/src/cli/sync.test.ts +594 -1
- package/src/cli/sync.ts +229 -65
- package/src/cognito-auth.test.ts +77 -0
- package/src/cognito-auth.ts +73 -11
- package/src/index.ts +8 -0
- package/src/journal.test.ts +72 -0
- package/src/journal.ts +95 -8
- package/src/machine-auth.test.ts +64 -2
- package/src/object-io.test.ts +142 -0
- package/src/object-io.ts +182 -30
- package/src/operation-lock.test.ts +63 -4
- package/src/operation-lock.ts +99 -31
- package/src/personal-vault.test.ts +42 -0
- package/src/personal-vault.ts +18 -3
- package/src/prefix-coalesce.test.ts +71 -1
- package/src/prefix-coalesce.ts +155 -30
- package/src/remote-pull.test.ts +205 -0
- package/src/remote-pull.ts +77 -14
- package/src/s3.test.ts +126 -0
- package/src/s3.ts +237 -122
- package/src/scope-shrink.ts +6 -3
- package/src/skill-telemetry.test.ts +109 -0
- package/src/skill-telemetry.ts +82 -14
- package/src/sync/event-sync.test.ts +75 -0
- package/src/sync/event-sync.ts +54 -1
- package/src/sync/metrics.test.ts +81 -0
- package/src/sync/metrics.ts +59 -4
- package/src/sync/pull-scope.ts +23 -7
- package/src/sync/push-receiver.test.ts +38 -1
- package/src/sync/push-receiver.ts +15 -18
- package/src/telemetry.test.ts +85 -0
- package/src/telemetry.ts +69 -6
- package/src/types.ts +8 -0
- package/src/watcher.test.ts +117 -0
- package/src/watcher.ts +209 -33
package/src/watcher.ts
CHANGED
|
@@ -163,7 +163,9 @@ export class WatchPushDriver {
|
|
|
163
163
|
}
|
|
164
164
|
this.timer = this.clock.setTimeout(() => {
|
|
165
165
|
this.timer = null;
|
|
166
|
-
void this.fire()
|
|
166
|
+
void this.fire().catch((err) => {
|
|
167
|
+
console.error("WatchPushDriver push failed:", err);
|
|
168
|
+
});
|
|
167
169
|
}, this.debounceMs);
|
|
168
170
|
}
|
|
169
171
|
|
|
@@ -400,6 +402,36 @@ function supportsRecursiveWatch(): boolean {
|
|
|
400
402
|
return process.platform === "darwin" || process.platform === "win32";
|
|
401
403
|
}
|
|
402
404
|
|
|
405
|
+
function startChokidarTreeWatch(
|
|
406
|
+
hqRoot: string,
|
|
407
|
+
shouldEmit: WatchPathFilter,
|
|
408
|
+
onEvent: (absolutePath: string) => void,
|
|
409
|
+
onError: (err: unknown) => void,
|
|
410
|
+
): WatchBackend {
|
|
411
|
+
const cw = watch(hqRoot, {
|
|
412
|
+
// chokidar fallback (Linux): see toChokidarIgnored for why the descent
|
|
413
|
+
// probe must not prune ancestor dirs of allowlisted leaves.
|
|
414
|
+
ignored: toChokidarIgnored(shouldEmit, hqRoot),
|
|
415
|
+
persistent: true,
|
|
416
|
+
ignoreInitial: true,
|
|
417
|
+
awaitWriteFinish: {
|
|
418
|
+
stabilityThreshold: 500,
|
|
419
|
+
pollInterval: 100,
|
|
420
|
+
},
|
|
421
|
+
});
|
|
422
|
+
cw.on("add", onEvent)
|
|
423
|
+
.on("change", onEvent)
|
|
424
|
+
.on("unlink", onEvent)
|
|
425
|
+
.on("addDir", onEvent)
|
|
426
|
+
.on("unlinkDir", onEvent)
|
|
427
|
+
.on("error", onError);
|
|
428
|
+
return {
|
|
429
|
+
close: () => {
|
|
430
|
+
void cw.close();
|
|
431
|
+
},
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
|
|
403
435
|
/**
|
|
404
436
|
* Start watching `hqRoot`, calling `onEvent(absolutePath)` for every change.
|
|
405
437
|
*
|
|
@@ -441,8 +473,39 @@ function startTreeWatch(
|
|
|
441
473
|
onEvent(path.resolve(hqRoot, rel));
|
|
442
474
|
},
|
|
443
475
|
);
|
|
444
|
-
|
|
445
|
-
|
|
476
|
+
let closed = false;
|
|
477
|
+
let fallback: WatchBackend | null = null;
|
|
478
|
+
native.on("error", (err) => {
|
|
479
|
+
onError(err);
|
|
480
|
+
if (closed || fallback !== null) return;
|
|
481
|
+
try {
|
|
482
|
+
native.close();
|
|
483
|
+
} catch {
|
|
484
|
+
/* already closed */
|
|
485
|
+
}
|
|
486
|
+
try {
|
|
487
|
+
fallback = startChokidarTreeWatch(
|
|
488
|
+
hqRoot,
|
|
489
|
+
shouldEmit,
|
|
490
|
+
onEvent,
|
|
491
|
+
onError,
|
|
492
|
+
);
|
|
493
|
+
} catch (fallbackErr) {
|
|
494
|
+
onError(fallbackErr);
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
return {
|
|
498
|
+
close: () => {
|
|
499
|
+
closed = true;
|
|
500
|
+
try {
|
|
501
|
+
native.close();
|
|
502
|
+
} catch {
|
|
503
|
+
/* already closed */
|
|
504
|
+
}
|
|
505
|
+
fallback?.close();
|
|
506
|
+
fallback = null;
|
|
507
|
+
},
|
|
508
|
+
};
|
|
446
509
|
} catch (err) {
|
|
447
510
|
// Recursive watch unexpectedly unavailable — fall back to chokidar
|
|
448
511
|
// rather than leaving the daemon with no watcher at all.
|
|
@@ -450,28 +513,7 @@ function startTreeWatch(
|
|
|
450
513
|
}
|
|
451
514
|
}
|
|
452
515
|
|
|
453
|
-
|
|
454
|
-
// chokidar fallback (Linux): see toChokidarIgnored for why the descent
|
|
455
|
-
// probe must not prune ancestor dirs of allowlisted leaves.
|
|
456
|
-
ignored: toChokidarIgnored(shouldEmit, hqRoot),
|
|
457
|
-
persistent: true,
|
|
458
|
-
ignoreInitial: true,
|
|
459
|
-
awaitWriteFinish: {
|
|
460
|
-
stabilityThreshold: 500,
|
|
461
|
-
pollInterval: 100,
|
|
462
|
-
},
|
|
463
|
-
});
|
|
464
|
-
cw.on("add", onEvent)
|
|
465
|
-
.on("change", onEvent)
|
|
466
|
-
.on("unlink", onEvent)
|
|
467
|
-
.on("addDir", onEvent)
|
|
468
|
-
.on("unlinkDir", onEvent)
|
|
469
|
-
.on("error", onError);
|
|
470
|
-
return {
|
|
471
|
-
close: () => {
|
|
472
|
-
void cw.close();
|
|
473
|
-
},
|
|
474
|
-
};
|
|
516
|
+
return startChokidarTreeWatch(hqRoot, shouldEmit, onEvent, onError);
|
|
475
517
|
}
|
|
476
518
|
|
|
477
519
|
/**
|
|
@@ -533,6 +575,25 @@ export function createWatchPathFilter(
|
|
|
533
575
|
};
|
|
534
576
|
}
|
|
535
577
|
|
|
578
|
+
export const DEFAULT_TREE_WATCHER_MAX_PENDING_PATHS = 4096;
|
|
579
|
+
export const DEFAULT_TREE_WATCHER_MAX_PENDING_BYTES = 1024 * 1024;
|
|
580
|
+
|
|
581
|
+
export interface TreeWatcherBacklogOverflow {
|
|
582
|
+
pendingPaths: number;
|
|
583
|
+
pendingBytes: number;
|
|
584
|
+
maxPendingPaths: number;
|
|
585
|
+
maxPendingBytes: number;
|
|
586
|
+
droppedPaths: number;
|
|
587
|
+
droppedBytes: number;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function estimatePendingEntryBytes(
|
|
591
|
+
absolutePath: string,
|
|
592
|
+
relativePath: string,
|
|
593
|
+
): number {
|
|
594
|
+
return Buffer.byteLength(absolutePath) + Buffer.byteLength(relativePath) + 64;
|
|
595
|
+
}
|
|
596
|
+
|
|
536
597
|
export interface TreeWatcherOptions {
|
|
537
598
|
/** Sync root to watch (== personal-vault root in personalMode). */
|
|
538
599
|
hqRoot: string;
|
|
@@ -547,6 +608,12 @@ export interface TreeWatcherOptions {
|
|
|
547
608
|
* from {@link createWatchPathFilter}.
|
|
548
609
|
*/
|
|
549
610
|
pathFilter?: WatchPathFilter;
|
|
611
|
+
/** Maximum distinct paths retained in one debounce window. */
|
|
612
|
+
maxPendingPaths?: number;
|
|
613
|
+
/** Approximate maximum path-string bytes retained in one debounce window. */
|
|
614
|
+
maxPendingBytes?: number;
|
|
615
|
+
/** Backlog overflow signal. Defaults to a console warning. */
|
|
616
|
+
onBacklogOverflow?: (info: TreeWatcherBacklogOverflow) => void;
|
|
550
617
|
}
|
|
551
618
|
|
|
552
619
|
/**
|
|
@@ -569,6 +636,12 @@ export interface TreeWatcherOptions {
|
|
|
569
636
|
export interface TreeChangeBatch {
|
|
570
637
|
/** Map of absolutePath → relativePath for every path in the settled burst. */
|
|
571
638
|
paths: Map<string, string>;
|
|
639
|
+
/** True when path detail was dropped after the watcher backlog cap was hit. */
|
|
640
|
+
overflowed?: boolean;
|
|
641
|
+
/** Count of paths dropped from the detailed batch after overflow. */
|
|
642
|
+
droppedPaths?: number;
|
|
643
|
+
/** Approximate path-string bytes dropped from the detailed batch. */
|
|
644
|
+
droppedBytes?: number;
|
|
572
645
|
}
|
|
573
646
|
|
|
574
647
|
/**
|
|
@@ -591,11 +664,19 @@ export class TreeWatcher {
|
|
|
591
664
|
private readonly debounceMs: number;
|
|
592
665
|
private readonly clock: Clock;
|
|
593
666
|
private readonly shouldEmit: WatchPathFilter;
|
|
667
|
+
private readonly maxPendingPaths: number;
|
|
668
|
+
private readonly maxPendingBytes: number;
|
|
669
|
+
private readonly onBacklogOverflow: (info: TreeWatcherBacklogOverflow) => void;
|
|
594
670
|
private backend: WatchBackend | null = null;
|
|
595
671
|
private timer: unknown = null;
|
|
596
672
|
private listeners = new Set<TreeChangeListener>();
|
|
597
673
|
/** Paths accumulated for the current (in-flight) debounce window. */
|
|
598
674
|
private pending = new Map<string, string>();
|
|
675
|
+
private pendingBytes = 0;
|
|
676
|
+
private overflowed = false;
|
|
677
|
+
private overflowLogged = false;
|
|
678
|
+
private droppedPaths = 0;
|
|
679
|
+
private droppedBytes = 0;
|
|
599
680
|
private disposed = false;
|
|
600
681
|
|
|
601
682
|
constructor(opts: TreeWatcherOptions) {
|
|
@@ -604,6 +685,21 @@ export class TreeWatcher {
|
|
|
604
685
|
this.clock = opts.clock ?? systemClock;
|
|
605
686
|
this.shouldEmit =
|
|
606
687
|
opts.pathFilter ?? createWatchPathFilter(opts.hqRoot, opts.personalMode ?? false);
|
|
688
|
+
this.maxPendingPaths = Math.max(
|
|
689
|
+
1,
|
|
690
|
+
opts.maxPendingPaths ?? DEFAULT_TREE_WATCHER_MAX_PENDING_PATHS,
|
|
691
|
+
);
|
|
692
|
+
this.maxPendingBytes = Math.max(
|
|
693
|
+
1,
|
|
694
|
+
opts.maxPendingBytes ?? DEFAULT_TREE_WATCHER_MAX_PENDING_BYTES,
|
|
695
|
+
);
|
|
696
|
+
this.onBacklogOverflow =
|
|
697
|
+
opts.onBacklogOverflow ??
|
|
698
|
+
((info) => {
|
|
699
|
+
console.warn(
|
|
700
|
+
`TreeWatcher backlog cap exceeded; dropping ${info.droppedPaths} path(s) until the next resync`,
|
|
701
|
+
);
|
|
702
|
+
});
|
|
607
703
|
}
|
|
608
704
|
|
|
609
705
|
/**
|
|
@@ -633,10 +729,19 @@ export class TreeWatcher {
|
|
|
633
729
|
this.hqRoot,
|
|
634
730
|
this.shouldEmit,
|
|
635
731
|
(absolutePath) => this.handleEvent(absolutePath),
|
|
636
|
-
(err) =>
|
|
732
|
+
(err) => {
|
|
733
|
+
console.error("TreeWatcher error:", err);
|
|
734
|
+
this.signalBackendResync();
|
|
735
|
+
},
|
|
637
736
|
);
|
|
638
737
|
}
|
|
639
738
|
|
|
739
|
+
private signalBackendResync(): void {
|
|
740
|
+
if (this.disposed) return;
|
|
741
|
+
this.overflowed = true;
|
|
742
|
+
this.arm();
|
|
743
|
+
}
|
|
744
|
+
|
|
640
745
|
/**
|
|
641
746
|
* Test/seam entry point: feed a raw filesystem path as if the backend
|
|
642
747
|
* reported it. Applies the emit filter then arms the debounce. Real watch
|
|
@@ -651,10 +756,42 @@ export class TreeWatcher {
|
|
|
651
756
|
if (!this.shouldEmit(absolutePath, false)) return;
|
|
652
757
|
const abs = path.resolve(absolutePath);
|
|
653
758
|
const rel = path.relative(this.hqRoot, abs).split(path.sep).join("/");
|
|
759
|
+
if (!this.pending.has(abs)) {
|
|
760
|
+
const entryBytes = estimatePendingEntryBytes(abs, rel);
|
|
761
|
+
if (
|
|
762
|
+
this.pending.size >= this.maxPendingPaths ||
|
|
763
|
+
this.pendingBytes + entryBytes > this.maxPendingBytes
|
|
764
|
+
) {
|
|
765
|
+
this.recordBacklogOverflow(entryBytes);
|
|
766
|
+
this.arm();
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
this.pendingBytes += entryBytes;
|
|
770
|
+
}
|
|
654
771
|
this.pending.set(abs, rel);
|
|
655
772
|
this.arm();
|
|
656
773
|
}
|
|
657
774
|
|
|
775
|
+
private recordBacklogOverflow(entryBytes: number): void {
|
|
776
|
+
this.overflowed = true;
|
|
777
|
+
this.droppedPaths += 1;
|
|
778
|
+
this.droppedBytes += entryBytes;
|
|
779
|
+
if (this.overflowLogged) return;
|
|
780
|
+
this.overflowLogged = true;
|
|
781
|
+
try {
|
|
782
|
+
this.onBacklogOverflow({
|
|
783
|
+
pendingPaths: this.pending.size,
|
|
784
|
+
pendingBytes: this.pendingBytes,
|
|
785
|
+
maxPendingPaths: this.maxPendingPaths,
|
|
786
|
+
maxPendingBytes: this.maxPendingBytes,
|
|
787
|
+
droppedPaths: this.droppedPaths,
|
|
788
|
+
droppedBytes: this.droppedBytes,
|
|
789
|
+
});
|
|
790
|
+
} catch (err) {
|
|
791
|
+
console.error("TreeWatcher backlog overflow logger error:", err);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
658
795
|
private arm(): void {
|
|
659
796
|
if (this.timer !== null) {
|
|
660
797
|
this.clock.clearTimeout(this.timer);
|
|
@@ -671,7 +808,12 @@ export class TreeWatcher {
|
|
|
671
808
|
// Snapshot + clear the accumulated paths so the next window starts fresh
|
|
672
809
|
// even if a listener re-enters synchronously.
|
|
673
810
|
const batch: TreeChangeBatch = { paths: new Map(this.pending) };
|
|
674
|
-
this.
|
|
811
|
+
if (this.overflowed) {
|
|
812
|
+
batch.overflowed = true;
|
|
813
|
+
batch.droppedPaths = this.droppedPaths;
|
|
814
|
+
batch.droppedBytes = this.droppedBytes;
|
|
815
|
+
}
|
|
816
|
+
this.clearPending();
|
|
675
817
|
// First changed relative path of the burst — the US-003 routing argument.
|
|
676
818
|
// undefined when the window settled with no captured path (e.g. a synthetic
|
|
677
819
|
// arm() with no handleEvent).
|
|
@@ -685,6 +827,15 @@ export class TreeWatcher {
|
|
|
685
827
|
}
|
|
686
828
|
}
|
|
687
829
|
|
|
830
|
+
private clearPending(): void {
|
|
831
|
+
this.pending.clear();
|
|
832
|
+
this.pendingBytes = 0;
|
|
833
|
+
this.overflowed = false;
|
|
834
|
+
this.overflowLogged = false;
|
|
835
|
+
this.droppedPaths = 0;
|
|
836
|
+
this.droppedBytes = 0;
|
|
837
|
+
}
|
|
838
|
+
|
|
688
839
|
/** True while the watch backend is active. */
|
|
689
840
|
isWatching(): boolean {
|
|
690
841
|
return this.backend !== null;
|
|
@@ -705,7 +856,7 @@ export class TreeWatcher {
|
|
|
705
856
|
this.clock.clearTimeout(this.timer);
|
|
706
857
|
this.timer = null;
|
|
707
858
|
}
|
|
708
|
-
this.
|
|
859
|
+
this.clearPending();
|
|
709
860
|
if (this.backend) {
|
|
710
861
|
this.backend.close();
|
|
711
862
|
this.backend = null;
|
|
@@ -792,6 +943,7 @@ export interface EmitterLogger {
|
|
|
792
943
|
* running TreeWatcher (returns an unsubscribe fn). Flag-gated + failure-safe.
|
|
793
944
|
*/
|
|
794
945
|
export class PushEventEmitter {
|
|
946
|
+
private static readonly MAX_CONCURRENT_PUBLISHES = 16;
|
|
795
947
|
private readonly originTenantId: string;
|
|
796
948
|
private readonly originDeviceId: string;
|
|
797
949
|
private readonly transport: PushTransport;
|
|
@@ -801,6 +953,7 @@ export class PushEventEmitter {
|
|
|
801
953
|
private readonly logger: EmitterLogger | undefined;
|
|
802
954
|
private internalSeq = 0;
|
|
803
955
|
private readonly nextSeq: () => number;
|
|
956
|
+
private publishTail: Promise<void> = Promise.resolve();
|
|
804
957
|
|
|
805
958
|
constructor(opts: PushEventEmitterOptions) {
|
|
806
959
|
this.originTenantId = opts.originTenantId;
|
|
@@ -848,12 +1001,35 @@ export class PushEventEmitter {
|
|
|
848
1001
|
*/
|
|
849
1002
|
async emitForBatch(batch: TreeChangeBatch): Promise<void> {
|
|
850
1003
|
if (!this.enabled) return;
|
|
851
|
-
const
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
1004
|
+
const entriesByRel = new Map<string, [string, string]>();
|
|
1005
|
+
for (const [absolutePath, relativePath] of batch.paths.entries()) {
|
|
1006
|
+
entriesByRel.set(relativePath, [absolutePath, relativePath]);
|
|
1007
|
+
}
|
|
1008
|
+
const entries = [...entriesByRel.values()];
|
|
1009
|
+
const run = this.publishTail.then(
|
|
1010
|
+
() => this.emitEntries(entries),
|
|
1011
|
+
() => this.emitEntries(entries),
|
|
856
1012
|
);
|
|
1013
|
+
this.publishTail = run.catch(() => undefined);
|
|
1014
|
+
await run;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
private async emitEntries(entries: Array<[string, string]>): Promise<void> {
|
|
1018
|
+
for (
|
|
1019
|
+
let i = 0;
|
|
1020
|
+
i < entries.length;
|
|
1021
|
+
i += PushEventEmitter.MAX_CONCURRENT_PUBLISHES
|
|
1022
|
+
) {
|
|
1023
|
+
const chunk = entries.slice(
|
|
1024
|
+
i,
|
|
1025
|
+
i + PushEventEmitter.MAX_CONCURRENT_PUBLISHES,
|
|
1026
|
+
);
|
|
1027
|
+
await Promise.all(
|
|
1028
|
+
chunk.map(([absolutePath, relativePath]) =>
|
|
1029
|
+
this.emitOne(absolutePath, relativePath),
|
|
1030
|
+
),
|
|
1031
|
+
);
|
|
1032
|
+
}
|
|
857
1033
|
}
|
|
858
1034
|
|
|
859
1035
|
private async emitOne(
|