@mulmoclaude/core 0.27.0 → 0.29.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/assets/helps/collection-skills.md +4 -2
- package/assets/helps/wiki.md +47 -2
- package/dist/collection/core/ontologyGraph.d.ts +6 -2
- package/dist/collection/index.cjs +4 -4
- package/dist/collection/index.cjs.map +1 -1
- package/dist/collection/index.js +4 -4
- package/dist/collection/index.js.map +1 -1
- package/dist/collection/registry/server/index.cjs +2 -2
- package/dist/collection/registry/server/index.js +2 -2
- package/dist/collection/server/backendAvailability.d.ts +5 -0
- package/dist/collection/server/csvStore.d.ts +0 -5
- package/dist/collection/server/index.cjs +4 -2
- package/dist/collection/server/index.d.ts +1 -0
- package/dist/collection/server/index.js +3 -3
- package/dist/collection/server/manageTool.d.ts +4 -0
- package/dist/collection/server/schemaDocs.d.ts +21 -0
- package/dist/collection/server/store.d.ts +37 -0
- package/dist/collection/server/watchFs.d.ts +17 -0
- package/dist/collection-watchers/index.cjs +114 -203
- package/dist/collection-watchers/index.cjs.map +1 -1
- package/dist/collection-watchers/index.d.ts +1 -1
- package/dist/collection-watchers/index.js +114 -202
- package/dist/collection-watchers/index.js.map +1 -1
- package/dist/collection-watchers/watcher.d.ts +8 -13
- package/dist/{discovery-B9Vfojyy.js → discovery-BbsJwVEq.js} +128 -15
- package/dist/discovery-BbsJwVEq.js.map +1 -0
- package/dist/{discovery-BxbJy2VN.cjs → discovery-Bklck7Ck.cjs} +137 -12
- package/dist/discovery-Bklck7Ck.cjs.map +1 -0
- package/dist/feeds/server/index.cjs +2 -2
- package/dist/feeds/server/index.js +2 -2
- package/dist/google/index.cjs +1 -1
- package/dist/google/index.js +1 -1
- package/dist/{promptSafety-0ZKHX-6J.cjs → promptSafety-BFt2g_wn.cjs} +2 -2
- package/dist/promptSafety-BFt2g_wn.cjs.map +1 -0
- package/dist/{promptSafety-RE1SPxBN.js → promptSafety-cZIeiZtB.js} +3 -3
- package/dist/promptSafety-cZIeiZtB.js.map +1 -0
- package/dist/remote-view/index.cjs +8 -0
- package/dist/remote-view/index.cjs.map +1 -1
- package/dist/remote-view/index.d.ts +7 -0
- package/dist/remote-view/index.js +8 -1
- package/dist/remote-view/index.js.map +1 -1
- package/dist/{server-DOGKfUuD.cjs → server-CghbYZFY.cjs} +199 -22
- package/dist/server-CghbYZFY.cjs.map +1 -0
- package/dist/{server-sGNe-msA.js → server-DlG6ydHO.js} +200 -23
- package/dist/server-DlG6ydHO.js.map +1 -0
- package/dist/whisper/client.cjs.map +1 -1
- package/dist/whisper/client.js.map +1 -1
- package/package.json +1 -1
- package/dist/discovery-B9Vfojyy.js.map +0 -1
- package/dist/discovery-BxbJy2VN.cjs.map +0 -1
- package/dist/promptSafety-0ZKHX-6J.cjs.map +0 -1
- package/dist/promptSafety-RE1SPxBN.js.map +0 -1
- package/dist/server-DOGKfUuD.cjs.map +0 -1
- package/dist/server-sGNe-msA.js.map +0 -1
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { p as fieldText } from "../ids-D1M1T6KJ.js";
|
|
2
|
-
import { S as itemIsDone, j as whenMatches } from "../promptSafety-
|
|
2
|
+
import { S as itemIsDone, j as whenMatches } from "../promptSafety-cZIeiZtB.js";
|
|
3
3
|
import "../collection/index.js";
|
|
4
|
-
import {
|
|
5
|
-
import { d as isTriggerDue, f as maybeSpawnSuccessor } from "../server-
|
|
4
|
+
import { K as itemFilePath, n as discoverCollections, r as loadCollection, rt as publishCollectionChange, u as storeFor } from "../discovery-BbsJwVEq.js";
|
|
5
|
+
import { d as isTriggerDue, f as maybeSpawnSuccessor } from "../server-DlG6ydHO.js";
|
|
6
6
|
import { d as publish, m as updateForPlugin, n as clear, s as listAll } from "../notifier-ChpY0XrY.js";
|
|
7
|
-
import
|
|
8
|
-
import { watch } from "node:fs";
|
|
9
|
-
import { access, mkdir } from "node:fs/promises";
|
|
7
|
+
import { access } from "node:fs/promises";
|
|
10
8
|
//#region src/collection-watchers/config.ts
|
|
11
9
|
var NOOP_LOG = {
|
|
12
10
|
info: () => {},
|
|
@@ -299,6 +297,15 @@ var watchers = /* @__PURE__ */ new Map();
|
|
|
299
297
|
var rediscoveryTimer = null;
|
|
300
298
|
var triggerTimer = null;
|
|
301
299
|
var started = false;
|
|
300
|
+
/** Guards the clock tick against overlapping itself. Paired with
|
|
301
|
+
* `watcherEpoch`: a teardown while a pass is in flight bumps the epoch, so
|
|
302
|
+
* that pass's `finally` knows it belongs to a dead generation and must not
|
|
303
|
+
* clear a guard the restarted watcher set now owns. `triggerTickInFlight`
|
|
304
|
+
* is what teardown AWAITS — clearing the flag alone would let a restart run
|
|
305
|
+
* a second pass alongside the first. */
|
|
306
|
+
var triggerTickRunning = false;
|
|
307
|
+
var watcherEpoch = 0;
|
|
308
|
+
var triggerTickInFlight = null;
|
|
302
309
|
/** Discovery options threaded into every `discoverCollections` /
|
|
303
310
|
* `loadCollection` / `sweepStaleActiveEntries` call. Production: empty
|
|
304
311
|
* (live workspace). Tests: `{ workspaceRoot, userSkillsDir }` pointing
|
|
@@ -306,15 +313,10 @@ var started = false;
|
|
|
306
313
|
* without threading through every signature. */
|
|
307
314
|
var discoveryOpts = {};
|
|
308
315
|
var itemSlots = /* @__PURE__ */ new Map();
|
|
309
|
-
/** Per-slug single-flight for a
|
|
310
|
-
*
|
|
311
|
-
* trailing re-run, mirroring the per-item slots
|
|
312
|
-
var
|
|
313
|
-
/** Trailing debounce per dataSource collection: an atomic file replace
|
|
314
|
-
* (Excel save, editor rename) surfaces as 2-3 fs events — collapse them
|
|
315
|
-
* into one change publish so live views refetch once. */
|
|
316
|
-
var DATA_SOURCE_DEBOUNCE_MS = 300;
|
|
317
|
-
var dataSourceTimers = /* @__PURE__ */ new Map();
|
|
316
|
+
/** Per-slug single-flight for a COLLECTION-granularity reconcile — a burst
|
|
317
|
+
* of changes the store couldn't attribute to a record collapses into one
|
|
318
|
+
* pass plus one trailing re-run, mirroring the per-item slots. */
|
|
319
|
+
var collectionSlots = /* @__PURE__ */ new Map();
|
|
318
320
|
/** Boot entry point: sweep stale active entries, then mount watchers for
|
|
319
321
|
* every discovered collection and arm the periodic re-discovery poll.
|
|
320
322
|
* Idempotent — a second call is a no-op. */
|
|
@@ -336,8 +338,15 @@ async function startCollectionWatchers(opts = {}) {
|
|
|
336
338
|
const triggerMs = opts.triggerTickIntervalMs === void 0 ? TRIGGER_TICK_INTERVAL_MS : opts.triggerTickIntervalMs;
|
|
337
339
|
if (triggerMs !== null) {
|
|
338
340
|
triggerTimer = setInterval(() => {
|
|
339
|
-
|
|
341
|
+
if (triggerTickRunning) return;
|
|
342
|
+
triggerTickRunning = true;
|
|
343
|
+
const epoch = watcherEpoch;
|
|
344
|
+
triggerTickInFlight = tickTimeTriggers().catch((err) => {
|
|
340
345
|
log().warn("watcher trigger tick failed", { error: errMsg(err) });
|
|
346
|
+
}).finally(() => {
|
|
347
|
+
if (epoch !== watcherEpoch) return;
|
|
348
|
+
triggerTickRunning = false;
|
|
349
|
+
triggerTickInFlight = null;
|
|
341
350
|
});
|
|
342
351
|
}, triggerMs);
|
|
343
352
|
triggerTimer.unref();
|
|
@@ -360,20 +369,22 @@ async function stopCollectionWatchers() {
|
|
|
360
369
|
clearInterval(triggerTimer);
|
|
361
370
|
triggerTimer = null;
|
|
362
371
|
}
|
|
372
|
+
await triggerTickInFlight;
|
|
373
|
+
triggerTickInFlight = null;
|
|
374
|
+
watcherEpoch += 1;
|
|
375
|
+
triggerTickRunning = false;
|
|
363
376
|
for (const watcher of watchers.values()) try {
|
|
364
|
-
watcher.
|
|
377
|
+
watcher.unsubscribe();
|
|
365
378
|
} catch {}
|
|
366
379
|
watchers.clear();
|
|
367
380
|
itemSlots.clear();
|
|
368
|
-
|
|
369
|
-
for (const timer of dataSourceTimers.values()) clearTimeout(timer);
|
|
370
|
-
dataSourceTimers.clear();
|
|
381
|
+
collectionSlots.clear();
|
|
371
382
|
discoveryOpts = {};
|
|
372
383
|
started = false;
|
|
373
384
|
}
|
|
374
385
|
/** Test-only: manually trigger one rediscovery + reconcile pass. */
|
|
375
386
|
async function _syncWatchersForTesting() {
|
|
376
|
-
|
|
387
|
+
return syncWatchers();
|
|
377
388
|
}
|
|
378
389
|
/** Test-only: drive one wall-clock tick synchronously, with an optional
|
|
379
390
|
* injected clock. */
|
|
@@ -397,7 +408,6 @@ async function tickTimeTriggers(now = evalNow()) {
|
|
|
397
408
|
});
|
|
398
409
|
continue;
|
|
399
410
|
}
|
|
400
|
-
if (schema.dataSource !== void 0) continue;
|
|
401
411
|
if (!schema.triggerField && !schema.spawn) continue;
|
|
402
412
|
await reconcileAllItems(entry.collection, discoveryOpts, now);
|
|
403
413
|
}
|
|
@@ -413,12 +423,14 @@ async function syncWatchers() {
|
|
|
413
423
|
collections = await discoverCollections(discoveryOpts);
|
|
414
424
|
} catch (err) {
|
|
415
425
|
log().warn("watcher discover failed", { error: errMsg(err) });
|
|
416
|
-
return;
|
|
426
|
+
return false;
|
|
417
427
|
}
|
|
418
428
|
const vanishedMutated = stopVanishedWatchers(new Set(collections.map((collection) => collection.slug)));
|
|
419
429
|
const schemaMutated = await reconcileChangedSchemas(collections);
|
|
420
430
|
const addedMutated = await startNewWatchers(collections);
|
|
421
|
-
if (vanishedMutated
|
|
431
|
+
if (!vanishedMutated && !schemaMutated && !addedMutated) return false;
|
|
432
|
+
await sweepStaleActiveEntries(discoveryOpts);
|
|
433
|
+
return true;
|
|
422
434
|
}
|
|
423
435
|
function stopVanishedWatchers(liveSlugs) {
|
|
424
436
|
let mutated = false;
|
|
@@ -426,7 +438,7 @@ function stopVanishedWatchers(liveSlugs) {
|
|
|
426
438
|
if (liveSlugs.has(slug)) continue;
|
|
427
439
|
const watcher = watchers.get(slug);
|
|
428
440
|
if (watcher) try {
|
|
429
|
-
watcher.
|
|
441
|
+
watcher.unsubscribe();
|
|
430
442
|
} catch {}
|
|
431
443
|
watchers.delete(slug);
|
|
432
444
|
mutated = true;
|
|
@@ -459,7 +471,7 @@ async function reconcileChangedSchemas(collections) {
|
|
|
459
471
|
if (storagePathChanged(existing.schemaJson, collection.schema)) {
|
|
460
472
|
log().info("watcher storage path changed, remounting", { slug: collection.slug });
|
|
461
473
|
try {
|
|
462
|
-
existing.
|
|
474
|
+
existing.unsubscribe();
|
|
463
475
|
} catch {}
|
|
464
476
|
watchers.delete(collection.slug);
|
|
465
477
|
if (collection.schema.dataSource !== void 0) publishCollectionChange({
|
|
@@ -471,199 +483,119 @@ async function reconcileChangedSchemas(collections) {
|
|
|
471
483
|
}
|
|
472
484
|
existing.schemaJson = nextJson;
|
|
473
485
|
existing.collection = collection;
|
|
474
|
-
if (collection.schema.dataSource !== void 0) {
|
|
475
|
-
log().info("dataSource watcher schema changed, publishing", { slug: collection.slug });
|
|
476
|
-
publishCollectionChange({
|
|
477
|
-
slug: collection.slug,
|
|
478
|
-
op: "upsert"
|
|
479
|
-
});
|
|
480
|
-
mutated = true;
|
|
481
|
-
continue;
|
|
482
|
-
}
|
|
483
486
|
log().info("watcher schema changed, re-reconciling", { slug: collection.slug });
|
|
484
487
|
await reconcileAllItems(collection, discoveryOpts);
|
|
488
|
+
if (collection.schema.dataSource !== void 0) publishCollectionChange({
|
|
489
|
+
slug: collection.slug,
|
|
490
|
+
op: "upsert"
|
|
491
|
+
});
|
|
485
492
|
mutated = true;
|
|
486
493
|
}
|
|
487
494
|
return mutated;
|
|
488
495
|
}
|
|
496
|
+
/** Mount a watcher for every collection that doesn't have one yet. Returns
|
|
497
|
+
* whether the watcher SET actually changed — the starters report whether
|
|
498
|
+
* they mounted, because ATTEMPTING is not mounting. A start that throws
|
|
499
|
+
* (logged and swallowed inside the starter) leaves `watchers` untouched, so
|
|
500
|
+
* the collection is retried next tick; counting that as a mutation made
|
|
501
|
+
* `syncWatchers` sweep on every tick for as long as the failure persisted. */
|
|
489
502
|
async function startNewWatchers(collections) {
|
|
490
503
|
let mutated = false;
|
|
491
504
|
for (const collection of collections) {
|
|
492
505
|
if (watchers.has(collection.slug)) continue;
|
|
493
|
-
if (
|
|
494
|
-
else if (collection.schema.storage !== void 0) await startStorageWatcher(collection);
|
|
495
|
-
else await startWatcherFor(collection);
|
|
496
|
-
mutated = true;
|
|
506
|
+
if (await startWatcherFor(collection)) mutated = true;
|
|
497
507
|
}
|
|
498
508
|
return mutated;
|
|
499
509
|
}
|
|
500
|
-
/**
|
|
501
|
-
*
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
}, DATA_SOURCE_DEBOUNCE_MS);
|
|
512
|
-
timer.unref?.();
|
|
513
|
-
dataSourceTimers.set(slug, timer);
|
|
514
|
-
}
|
|
515
|
-
/** Watch a dataSource collection's external data file. The watch mounts
|
|
516
|
-
* on the PARENT directory (watching the file itself goes stale after an
|
|
517
|
-
* atomic replace — the inode changes) and filters events to the file's
|
|
518
|
-
* basename; a null filename (platform quirk) is treated as a hit. No
|
|
519
|
-
* reconciler involvement — replacing the CSV just refreshes the views. */
|
|
520
|
-
async function startDataSourceWatcher(collection) {
|
|
521
|
-
const file = collection.dataSourceFile;
|
|
522
|
-
if (file === void 0) return;
|
|
523
|
-
const dir = path.dirname(file);
|
|
524
|
-
const base = path.basename(file);
|
|
525
|
-
try {
|
|
526
|
-
await mkdir(dir, { recursive: true });
|
|
527
|
-
const watcher = watch(dir, { persistent: false }, (_eventType, filename) => {
|
|
528
|
-
if (filename !== null && filename !== base) return;
|
|
529
|
-
scheduleDataSourcePublish(collection.slug);
|
|
530
|
-
});
|
|
531
|
-
watcher.on("error", (err) => {
|
|
532
|
-
log().warn("dataSource watcher error", {
|
|
533
|
-
slug: collection.slug,
|
|
534
|
-
error: errMsg(err)
|
|
535
|
-
});
|
|
536
|
-
});
|
|
537
|
-
watchers.set(collection.slug, {
|
|
538
|
-
slug: collection.slug,
|
|
539
|
-
dataDir: dir,
|
|
540
|
-
watcher,
|
|
541
|
-
schemaJson: JSON.stringify(collection.schema),
|
|
542
|
-
collection
|
|
543
|
-
});
|
|
544
|
-
log().info("dataSource watcher started", {
|
|
545
|
-
slug: collection.slug,
|
|
546
|
-
file
|
|
547
|
-
});
|
|
548
|
-
} catch (err) {
|
|
549
|
-
log().warn("dataSource watcher start failed", {
|
|
550
|
-
slug: collection.slug,
|
|
551
|
-
error: errMsg(err)
|
|
552
|
-
});
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
/** Watch a `storage` collection's database file. One db file holds every
|
|
556
|
-
* record, so an event can't name WHICH record changed — each (debounced)
|
|
557
|
-
* event runs a full `reconcileAllItems` pass (bells / spawn) plus a
|
|
558
|
-
* change publish so views refetch after EXTERNAL edits too (host writes
|
|
559
|
-
* already publish their own change events; the extra ping is debounced
|
|
560
|
-
* and idempotent). Mounts on the parent dir, same as the dataSource
|
|
561
|
-
* watcher, so an atomic replace can't strand the watch. */
|
|
562
|
-
async function startStorageWatcher(collection) {
|
|
563
|
-
const file = collection.storageFile;
|
|
564
|
-
if (file === void 0) return;
|
|
565
|
-
const dir = path.dirname(file);
|
|
566
|
-
const base = path.basename(file);
|
|
510
|
+
/** Mount one collection's change subscription, whatever its backend.
|
|
511
|
+
*
|
|
512
|
+
* The store decides how to detect a change and at what granularity; this
|
|
513
|
+
* decides what to do about one. That split is the point: adding a backend
|
|
514
|
+
* means implementing `watch` on its store, not another branch here.
|
|
515
|
+
*
|
|
516
|
+
* A store without `watch` cannot report external changes at all — it is
|
|
517
|
+
* still registered (so bells reconcile at boot and on the clock tick), just
|
|
518
|
+
* without live updates. */
|
|
519
|
+
async function startWatcherFor(collection) {
|
|
520
|
+
const { slug } = collection;
|
|
567
521
|
try {
|
|
568
|
-
await mkdir(dir, { recursive: true });
|
|
569
522
|
await reconcileAllItems(collection, discoveryOpts);
|
|
570
|
-
const
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
slug: collection.slug,
|
|
523
|
+
const store = storeFor(collection, discoveryOpts);
|
|
524
|
+
const unsubscribe = store.watch ? await store.watch((change) => {
|
|
525
|
+
handleStoreChange(slug, change).catch((err) => {
|
|
526
|
+
log().warn("store change handling failed", {
|
|
527
|
+
slug,
|
|
576
528
|
error: errMsg(err)
|
|
577
529
|
});
|
|
578
530
|
});
|
|
579
|
-
});
|
|
580
|
-
|
|
581
|
-
log().warn("
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
dataDir: dir,
|
|
589
|
-
watcher,
|
|
531
|
+
}) : () => {};
|
|
532
|
+
if (unsubscribe === null) {
|
|
533
|
+
log().warn("collection watcher could not arm, retrying next sync", { slug });
|
|
534
|
+
return false;
|
|
535
|
+
}
|
|
536
|
+
watchers.set(slug, {
|
|
537
|
+
slug,
|
|
538
|
+
dataDir: collection.dataDir,
|
|
539
|
+
unsubscribe,
|
|
590
540
|
schemaJson: JSON.stringify(collection.schema),
|
|
591
541
|
collection
|
|
592
542
|
});
|
|
593
|
-
log().info("
|
|
594
|
-
slug
|
|
595
|
-
|
|
543
|
+
log().info("collection watcher started", {
|
|
544
|
+
slug,
|
|
545
|
+
live: store.watch !== void 0
|
|
596
546
|
});
|
|
547
|
+
return true;
|
|
597
548
|
} catch (err) {
|
|
598
|
-
log().warn("
|
|
599
|
-
slug
|
|
549
|
+
log().warn("collection watcher start failed", {
|
|
550
|
+
slug,
|
|
600
551
|
error: errMsg(err)
|
|
601
552
|
});
|
|
553
|
+
return false;
|
|
602
554
|
}
|
|
603
555
|
}
|
|
604
|
-
|
|
605
|
-
|
|
556
|
+
/** React to one reported change. Backend-agnostic by construction — it sees
|
|
557
|
+
* only the granularity the store reported.
|
|
558
|
+
*
|
|
559
|
+
* `item`: reconcile just that record, then publish it. `collection`: the
|
|
560
|
+
* store couldn't say which record, so re-derive everything and pair it with
|
|
561
|
+
* a sweep — a record deleted remotely leaves a bell that a walk over the
|
|
562
|
+
* SURVIVING records can never clear. */
|
|
563
|
+
async function handleStoreChange(slug, change) {
|
|
564
|
+
if (change.kind === "collection") {
|
|
565
|
+
await scheduleCollectionReconcile(slug);
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
const current = watchers.get(slug)?.collection;
|
|
569
|
+
if (!current) return;
|
|
570
|
+
await scheduleItemReconcile(current, change.itemId);
|
|
571
|
+
}
|
|
572
|
+
/** Full re-derivation for a collection-granularity change, single-flighted
|
|
573
|
+
* per slug so a burst of writes collapses into one pass plus one trailing
|
|
574
|
+
* re-run. */
|
|
575
|
+
function scheduleCollectionReconcile(slug) {
|
|
576
|
+
return runSingleFlight(collectionSlots, slug, async () => {
|
|
606
577
|
const collection = await loadCollection(slug, discoveryOpts);
|
|
607
578
|
if (!collection) return;
|
|
608
579
|
await reconcileAllItems(collection, discoveryOpts);
|
|
609
580
|
await sweepStaleActiveEntries(discoveryOpts);
|
|
610
|
-
|
|
581
|
+
}, () => {
|
|
582
|
+
safePublish({
|
|
611
583
|
slug,
|
|
612
584
|
op: "upsert"
|
|
613
585
|
});
|
|
586
|
+
return Promise.resolve();
|
|
614
587
|
});
|
|
615
588
|
}
|
|
616
|
-
/** Test-only:
|
|
617
|
-
|
|
618
|
-
|
|
589
|
+
/** Test-only: feed one store-reported change through the same path a live
|
|
590
|
+
* subscription uses, so a test can pin how a change is reacted to without
|
|
591
|
+
* depending on fs.watch timing. */
|
|
592
|
+
function _handleStoreChangeForTesting(slug, change) {
|
|
593
|
+
return handleStoreChange(slug, change);
|
|
619
594
|
}
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
await mkdir(dataDir, { recursive: true });
|
|
624
|
-
await reconcileAllItems(collection, discoveryOpts);
|
|
625
|
-
const watcher = watch(dataDir, { persistent: false }, (_eventType, filename) => {
|
|
626
|
-
onEvent(slug, filename).catch((err) => {
|
|
627
|
-
log().warn("watcher event failed", {
|
|
628
|
-
slug,
|
|
629
|
-
filename,
|
|
630
|
-
error: errMsg(err)
|
|
631
|
-
});
|
|
632
|
-
});
|
|
633
|
-
});
|
|
634
|
-
watcher.on("error", (err) => {
|
|
635
|
-
log().warn("watcher error", {
|
|
636
|
-
slug,
|
|
637
|
-
error: errMsg(err)
|
|
638
|
-
});
|
|
639
|
-
});
|
|
640
|
-
watchers.set(slug, {
|
|
641
|
-
slug,
|
|
642
|
-
dataDir,
|
|
643
|
-
watcher,
|
|
644
|
-
schemaJson: JSON.stringify(schema),
|
|
645
|
-
collection
|
|
646
|
-
});
|
|
647
|
-
log().info("watcher started", {
|
|
648
|
-
slug,
|
|
649
|
-
dataDir
|
|
650
|
-
});
|
|
651
|
-
} catch (err) {
|
|
652
|
-
log().warn("watcher start failed", {
|
|
653
|
-
slug,
|
|
654
|
-
error: errMsg(err)
|
|
655
|
-
});
|
|
656
|
-
}
|
|
595
|
+
/** Test-only: drive one collection-granularity reconcile directly. */
|
|
596
|
+
function _scheduleCollectionReconcileForTesting(slug) {
|
|
597
|
+
return scheduleCollectionReconcile(slug);
|
|
657
598
|
}
|
|
658
|
-
/** Test-only: the per-key single-flight scheduler. Exported so test code
|
|
659
|
-
* can drive rapid-fire calls directly and observe the trailing coalesce
|
|
660
|
-
* — `fs.watch` event timing is too flaky to assert against.
|
|
661
|
-
*
|
|
662
|
-
* Single-flight semantics: while a reconcile is in flight for a given
|
|
663
|
-
* (slug, itemId), additional events on the same key set `pending = true`
|
|
664
|
-
* and return — the running reconcile re-runs once after it completes.
|
|
665
|
-
* This collapses fs.watch's rapid-fire bursts (atomic rename surfaces as
|
|
666
|
-
* 2-3 events) into a single reconcile + one trailing re-run. */
|
|
667
599
|
function _scheduleItemReconcileForTesting(collection, itemId) {
|
|
668
600
|
return scheduleItemReconcile(collection, itemId);
|
|
669
601
|
}
|
|
@@ -737,27 +669,7 @@ function safePublish(payload) {
|
|
|
737
669
|
});
|
|
738
670
|
}
|
|
739
671
|
}
|
|
740
|
-
/** Handle a single fs.watch event. Re-loads the collection (schema may
|
|
741
|
-
* have changed since startup), filters out non-record files, and
|
|
742
|
-
* forwards to the single-flighted reconciler. `filename === null` (rare,
|
|
743
|
-
* platform-specific) triggers a full directory rescan to be safe. */
|
|
744
|
-
async function onEvent(slug, filename) {
|
|
745
|
-
const collection = await loadCollection(slug, discoveryOpts);
|
|
746
|
-
if (!collection) return;
|
|
747
|
-
if (filename === null) {
|
|
748
|
-
try {
|
|
749
|
-
await reconcileAllItems(collection, discoveryOpts);
|
|
750
|
-
await sweepStaleActiveEntries(discoveryOpts);
|
|
751
|
-
} finally {
|
|
752
|
-
safePublish({ slug });
|
|
753
|
-
}
|
|
754
|
-
return;
|
|
755
|
-
}
|
|
756
|
-
const name = typeof filename === "string" ? filename : filename.toString("utf-8");
|
|
757
|
-
if (!name.endsWith(".json") || name.startsWith(".")) return;
|
|
758
|
-
await scheduleItemReconcile(collection, name.slice(0, -5));
|
|
759
|
-
}
|
|
760
672
|
//#endregion
|
|
761
|
-
export { _resetReconcilerLocksForTesting,
|
|
673
|
+
export { _handleStoreChangeForTesting, _resetReconcilerLocksForTesting, _scheduleCollectionReconcileForTesting, _scheduleItemReconcileForTesting, _syncWatchersForTesting, _tickTimeTriggersForTesting, clearItemNotification, configureCollectionWatchers, evalNow, itemIsDone, reconcileAllItems, reconcileItem, resetCollectionWatchersConfig, resolveDisplayLabel, startCollectionWatchers, stopCollectionWatchers, sweepStaleActiveEntries };
|
|
762
674
|
|
|
763
675
|
//# sourceMappingURL=index.js.map
|