@gmickel/gno 1.34.4 → 1.34.6
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 +11 -1
- package/THIRD_PARTY_NOTICES.md +16 -0
- package/browser-extension/artifacts/{gno-browser-clipper-v1.34.4.zip → gno-browser-clipper-v1.34.6.zip} +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.34.6.zip.sha256 +1 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/package.json +5 -1
- package/spec/cli.md +16 -10
- package/src/cli/pager.ts +29 -14
- package/src/ingestion/sync.ts +368 -84
- package/src/serve/public/globals.built.css +1 -1
- package/src/serve/watch-reconciliation-fallback-disk.ts +220 -0
- package/src/serve/watch-reconciliation-fallback.ts +404 -0
- package/src/serve/watch-reconciliation-shared.ts +343 -0
- package/src/serve/watch-reconciliation.ts +122 -0
- package/src/serve/watch-service-events.ts +261 -0
- package/src/serve/watch-service-flush-generation.ts +140 -0
- package/src/serve/watch-service-flush-helpers.ts +147 -0
- package/src/serve/watch-service-flush.ts +433 -0
- package/src/serve/watch-service-hosts.ts +109 -0
- package/src/serve/watch-service-lifecycle.ts +219 -0
- package/src/serve/watch-service-run-flush.ts +236 -0
- package/src/serve/watch-service-snapshot.ts +101 -0
- package/src/serve/watch-service-state.ts +146 -0
- package/src/serve/watch-service.ts +265 -306
- package/src/serve/watch-snapshot-handles.ts +285 -0
- package/src/serve/watch-snapshot-libc.ts +391 -0
- package/src/serve/watch-snapshot-ops.ts +399 -0
- package/src/serve/watch-snapshot-resolve.ts +246 -0
- package/src/serve/watch-snapshot-scan.ts +297 -0
- package/src/serve/watch-snapshot-types.ts +350 -0
- package/src/serve/watch-snapshot.ts +51 -0
- package/src/store/index.ts +1 -1
- package/src/store/sqlite/adapter.ts +191 -0
- package/src/store/types.ts +66 -0
- package/vendor/fts5-snowball/README.md +5 -1
- package/vendor/fts5-snowball/darwin-x64/fts5stemmer.dylib +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.34.4.zip.sha256 +0 -1
package/src/ingestion/sync.ts
CHANGED
|
@@ -616,6 +616,31 @@ class Semaphore {
|
|
|
616
616
|
}
|
|
617
617
|
}
|
|
618
618
|
|
|
619
|
+
function summarizePathResults(
|
|
620
|
+
collection: string,
|
|
621
|
+
results: FileSyncResult[],
|
|
622
|
+
markedInactive: number,
|
|
623
|
+
startedAt: number,
|
|
624
|
+
errors: CollectionSyncResult["errors"]
|
|
625
|
+
): CollectionSyncResult {
|
|
626
|
+
return {
|
|
627
|
+
collection,
|
|
628
|
+
filesProcessed: results.length,
|
|
629
|
+
filesAdded: results.filter((result) => result.status === "added").length,
|
|
630
|
+
filesUpdated: results.filter((result) => result.status === "updated")
|
|
631
|
+
.length,
|
|
632
|
+
filesUnchanged: results.filter((result) => result.status === "unchanged")
|
|
633
|
+
.length,
|
|
634
|
+
filesErrored: results.filter((result) => result.status === "error").length,
|
|
635
|
+
filesSkipped: results.filter((result) => result.status === "skipped")
|
|
636
|
+
.length,
|
|
637
|
+
filesMarkedInactive: markedInactive,
|
|
638
|
+
durationMs: Date.now() - startedAt,
|
|
639
|
+
files: results,
|
|
640
|
+
errors,
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
|
|
619
644
|
/**
|
|
620
645
|
* Sync service implementation.
|
|
621
646
|
*/
|
|
@@ -1161,6 +1186,54 @@ export class SyncService {
|
|
|
1161
1186
|
return result.files ?? [];
|
|
1162
1187
|
}
|
|
1163
1188
|
|
|
1189
|
+
/**
|
|
1190
|
+
* Inactivate proven-absent index sources without requiring disk absence.
|
|
1191
|
+
* Used when classification proves a path is no longer an eligible file
|
|
1192
|
+
* source even if a directory/FIFO/device now occupies the path.
|
|
1193
|
+
* Preserves record-container fan-out and typed-edge projection.
|
|
1194
|
+
*/
|
|
1195
|
+
async inactivateAbsentSources(
|
|
1196
|
+
collection: Collection,
|
|
1197
|
+
store: StorePort,
|
|
1198
|
+
relPaths: string[],
|
|
1199
|
+
options: SyncOptions = {}
|
|
1200
|
+
): Promise<CollectionSyncResult> {
|
|
1201
|
+
const startedAt = Date.now();
|
|
1202
|
+
const syncOptions: SyncOptions = {
|
|
1203
|
+
...options,
|
|
1204
|
+
contentTypeRules: options.contentTypeRules ?? [],
|
|
1205
|
+
contentTypeRulesFingerprint:
|
|
1206
|
+
options.contentTypeRulesFingerprint ??
|
|
1207
|
+
fingerprintContentTypeMetadataRules(options.contentTypeRules ?? []),
|
|
1208
|
+
};
|
|
1209
|
+
const results: FileSyncResult[] = [];
|
|
1210
|
+
const projectionSourceIds = new Set<number>();
|
|
1211
|
+
let markedInactive = 0;
|
|
1212
|
+
|
|
1213
|
+
for (const relPath of relPaths) {
|
|
1214
|
+
const outcome = await this.inactivateOneAbsentSource(
|
|
1215
|
+
collection,
|
|
1216
|
+
store,
|
|
1217
|
+
relPath,
|
|
1218
|
+
projectionSourceIds
|
|
1219
|
+
);
|
|
1220
|
+
results.push(outcome.result);
|
|
1221
|
+
markedInactive += outcome.markedInactive;
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
const errors =
|
|
1225
|
+
syncOptions.projectTypedEdges === false
|
|
1226
|
+
? []
|
|
1227
|
+
: await this.projectTypedEdges(store, syncOptions, projectionSourceIds);
|
|
1228
|
+
return summarizePathResults(
|
|
1229
|
+
collection.name,
|
|
1230
|
+
results,
|
|
1231
|
+
markedInactive,
|
|
1232
|
+
startedAt,
|
|
1233
|
+
errors
|
|
1234
|
+
);
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1164
1237
|
async syncPaths(
|
|
1165
1238
|
collection: Collection,
|
|
1166
1239
|
store: StorePort,
|
|
@@ -1195,20 +1268,52 @@ export class SyncService {
|
|
|
1195
1268
|
}
|
|
1196
1269
|
const recordDocuments = recordDocumentsResult.value;
|
|
1197
1270
|
const existingResult = await store.getDocument(collection.name, relPath);
|
|
1198
|
-
|
|
1271
|
+
if (!existingResult.ok) {
|
|
1272
|
+
results.push({
|
|
1273
|
+
relPath,
|
|
1274
|
+
status: "error",
|
|
1275
|
+
errorCode: existingResult.error.code,
|
|
1276
|
+
errorMessage: existingResult.error.message,
|
|
1277
|
+
});
|
|
1278
|
+
continue;
|
|
1279
|
+
}
|
|
1280
|
+
const existingDoc = existingResult.value;
|
|
1199
1281
|
if (existingDoc) {
|
|
1200
|
-
await this.collectProjectionSourceIds(
|
|
1282
|
+
const collectError = await this.collectProjectionSourceIds(
|
|
1201
1283
|
store,
|
|
1202
1284
|
existingDoc.id,
|
|
1203
1285
|
projectionSourceIds
|
|
1204
1286
|
);
|
|
1287
|
+
if (collectError) {
|
|
1288
|
+
results.push({
|
|
1289
|
+
relPath,
|
|
1290
|
+
status: "error",
|
|
1291
|
+
errorCode: collectError.code,
|
|
1292
|
+
errorMessage: collectError.message,
|
|
1293
|
+
});
|
|
1294
|
+
continue;
|
|
1295
|
+
}
|
|
1205
1296
|
}
|
|
1297
|
+
let recordCollectFailed: FileSyncResult | null = null;
|
|
1206
1298
|
for (const recordDocument of recordDocuments) {
|
|
1207
|
-
await this.collectProjectionSourceIds(
|
|
1299
|
+
const collectError = await this.collectProjectionSourceIds(
|
|
1208
1300
|
store,
|
|
1209
1301
|
recordDocument.id,
|
|
1210
1302
|
projectionSourceIds
|
|
1211
1303
|
);
|
|
1304
|
+
if (collectError) {
|
|
1305
|
+
recordCollectFailed = {
|
|
1306
|
+
relPath,
|
|
1307
|
+
status: "error",
|
|
1308
|
+
errorCode: collectError.code,
|
|
1309
|
+
errorMessage: collectError.message,
|
|
1310
|
+
};
|
|
1311
|
+
break;
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
if (recordCollectFailed) {
|
|
1315
|
+
results.push(recordCollectFailed);
|
|
1316
|
+
continue;
|
|
1212
1317
|
}
|
|
1213
1318
|
|
|
1214
1319
|
const absPath = join(collection.path, relPath);
|
|
@@ -1230,39 +1335,15 @@ export class SyncService {
|
|
|
1230
1335
|
});
|
|
1231
1336
|
continue;
|
|
1232
1337
|
}
|
|
1233
|
-
const
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
.filter((document) => document.active)
|
|
1237
|
-
.map((document) => document.relPath),
|
|
1238
|
-
];
|
|
1239
|
-
if (activePaths.length > 0) {
|
|
1240
|
-
const inactiveResult = await store.markInactive(
|
|
1241
|
-
collection.name,
|
|
1242
|
-
activePaths
|
|
1243
|
-
);
|
|
1244
|
-
if (!inactiveResult.ok) {
|
|
1245
|
-
results.push({
|
|
1246
|
-
relPath,
|
|
1247
|
-
status: "error",
|
|
1248
|
-
errorCode: inactiveResult.error.code,
|
|
1249
|
-
errorMessage: inactiveResult.error.message,
|
|
1250
|
-
});
|
|
1251
|
-
continue;
|
|
1252
|
-
}
|
|
1253
|
-
markedInactive += inactiveResult.value;
|
|
1254
|
-
results.push({
|
|
1255
|
-
relPath,
|
|
1256
|
-
status: "updated",
|
|
1257
|
-
docid: existingDoc?.docid,
|
|
1258
|
-
});
|
|
1259
|
-
continue;
|
|
1260
|
-
}
|
|
1261
|
-
results.push({
|
|
1338
|
+
const inactive = await this.inactivateOneAbsentSource(
|
|
1339
|
+
collection,
|
|
1340
|
+
store,
|
|
1262
1341
|
relPath,
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1342
|
+
projectionSourceIds,
|
|
1343
|
+
{ existingDoc, recordDocuments }
|
|
1344
|
+
);
|
|
1345
|
+
results.push(inactive.result);
|
|
1346
|
+
markedInactive += inactive.markedInactive;
|
|
1266
1347
|
continue;
|
|
1267
1348
|
}
|
|
1268
1349
|
|
|
@@ -1322,28 +1403,67 @@ export class SyncService {
|
|
|
1322
1403
|
syncOptions
|
|
1323
1404
|
);
|
|
1324
1405
|
results.push(result);
|
|
1406
|
+
if (result.status === "error" || result.status === "skipped") {
|
|
1407
|
+
continue;
|
|
1408
|
+
}
|
|
1409
|
+
// Post-write projection identity must succeed so typed-edge cleanup can run.
|
|
1325
1410
|
const currentResult = await store.getDocument(collection.name, relPath);
|
|
1326
|
-
|
|
1411
|
+
if (!currentResult.ok) {
|
|
1412
|
+
results[results.length - 1] = {
|
|
1413
|
+
relPath,
|
|
1414
|
+
status: "error",
|
|
1415
|
+
errorCode: currentResult.error.code,
|
|
1416
|
+
errorMessage: currentResult.error.message,
|
|
1417
|
+
};
|
|
1418
|
+
continue;
|
|
1419
|
+
}
|
|
1420
|
+
const currentDoc = currentResult.value;
|
|
1327
1421
|
if (currentDoc?.active) {
|
|
1328
|
-
await this.collectProjectionSourceIds(
|
|
1422
|
+
const collectError = await this.collectProjectionSourceIds(
|
|
1329
1423
|
store,
|
|
1330
1424
|
currentDoc.id,
|
|
1331
1425
|
projectionSourceIds
|
|
1332
1426
|
);
|
|
1427
|
+
if (collectError) {
|
|
1428
|
+
results[results.length - 1] = {
|
|
1429
|
+
relPath,
|
|
1430
|
+
status: "error",
|
|
1431
|
+
errorCode: collectError.code,
|
|
1432
|
+
errorMessage: collectError.message,
|
|
1433
|
+
};
|
|
1434
|
+
continue;
|
|
1435
|
+
}
|
|
1333
1436
|
}
|
|
1334
1437
|
const currentDocuments = await store.listRecordDocuments(
|
|
1335
1438
|
collection.name,
|
|
1336
1439
|
relPath
|
|
1337
1440
|
);
|
|
1338
|
-
if (currentDocuments.ok) {
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1441
|
+
if (!currentDocuments.ok) {
|
|
1442
|
+
results[results.length - 1] = {
|
|
1443
|
+
relPath,
|
|
1444
|
+
status: "error",
|
|
1445
|
+
errorCode: currentDocuments.error.code,
|
|
1446
|
+
errorMessage: currentDocuments.error.message,
|
|
1447
|
+
};
|
|
1448
|
+
continue;
|
|
1449
|
+
}
|
|
1450
|
+
for (const recordDocument of currentDocuments.value) {
|
|
1451
|
+
if (!recordDocument.active) {
|
|
1452
|
+
continue;
|
|
1453
|
+
}
|
|
1454
|
+
const collectError = await this.collectProjectionSourceIds(
|
|
1455
|
+
store,
|
|
1456
|
+
recordDocument.id,
|
|
1457
|
+
projectionSourceIds
|
|
1458
|
+
);
|
|
1459
|
+
if (collectError) {
|
|
1460
|
+
results[results.length - 1] = {
|
|
1461
|
+
relPath,
|
|
1462
|
+
status: "error",
|
|
1463
|
+
errorCode: collectError.code,
|
|
1464
|
+
errorMessage: collectError.message,
|
|
1465
|
+
};
|
|
1466
|
+
break;
|
|
1347
1467
|
}
|
|
1348
1468
|
}
|
|
1349
1469
|
}
|
|
@@ -1352,55 +1472,184 @@ export class SyncService {
|
|
|
1352
1472
|
syncOptions.projectTypedEdges === false
|
|
1353
1473
|
? []
|
|
1354
1474
|
: await this.projectTypedEdges(store, syncOptions, projectionSourceIds);
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
)
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1475
|
+
return summarizePathResults(
|
|
1476
|
+
collection.name,
|
|
1477
|
+
results,
|
|
1478
|
+
markedInactive,
|
|
1479
|
+
startedAt,
|
|
1480
|
+
errors
|
|
1481
|
+
);
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
/**
|
|
1485
|
+
* Soft-delete active docs/record-container children for a proven-absent source.
|
|
1486
|
+
* Does not inspect disk — callers prove absence of an indexable file source.
|
|
1487
|
+
* Store lookup / backlink / mark failures surface as per-file errors (never
|
|
1488
|
+
* silent null/skip) so callers cannot advance snapshot authority.
|
|
1489
|
+
*/
|
|
1490
|
+
private async inactivateOneAbsentSource(
|
|
1491
|
+
collection: Collection,
|
|
1492
|
+
store: StorePort,
|
|
1493
|
+
relPath: string,
|
|
1494
|
+
projectionSourceIds: Set<number>,
|
|
1495
|
+
preloaded?: {
|
|
1496
|
+
existingDoc: { id: number; active: boolean; docid?: string } | null;
|
|
1497
|
+
recordDocuments: Array<{ id: number; active: boolean; relPath: string }>;
|
|
1498
|
+
}
|
|
1499
|
+
): Promise<{ result: FileSyncResult; markedInactive: number }> {
|
|
1500
|
+
let existingDoc = preloaded?.existingDoc ?? null;
|
|
1501
|
+
let recordDocuments = preloaded?.recordDocuments;
|
|
1502
|
+
|
|
1503
|
+
if (!preloaded) {
|
|
1504
|
+
const recordDocumentsResult = await store.listRecordDocuments(
|
|
1505
|
+
collection.name,
|
|
1506
|
+
relPath
|
|
1507
|
+
);
|
|
1508
|
+
if (!recordDocumentsResult.ok) {
|
|
1509
|
+
return {
|
|
1510
|
+
result: {
|
|
1511
|
+
relPath,
|
|
1512
|
+
status: "error",
|
|
1513
|
+
errorCode: recordDocumentsResult.error.code,
|
|
1514
|
+
errorMessage: recordDocumentsResult.error.message,
|
|
1515
|
+
},
|
|
1516
|
+
markedInactive: 0,
|
|
1517
|
+
};
|
|
1518
|
+
}
|
|
1519
|
+
recordDocuments = recordDocumentsResult.value;
|
|
1520
|
+
const existingResult = await store.getDocument(collection.name, relPath);
|
|
1521
|
+
if (!existingResult.ok) {
|
|
1522
|
+
// ok:false is a hard per-file error — never treat as missing/skip.
|
|
1523
|
+
return {
|
|
1524
|
+
result: {
|
|
1525
|
+
relPath,
|
|
1526
|
+
status: "error",
|
|
1527
|
+
errorCode: existingResult.error.code,
|
|
1528
|
+
errorMessage: existingResult.error.message,
|
|
1529
|
+
},
|
|
1530
|
+
markedInactive: 0,
|
|
1531
|
+
};
|
|
1532
|
+
}
|
|
1533
|
+
existingDoc = existingResult.value;
|
|
1534
|
+
if (existingDoc) {
|
|
1535
|
+
const collectError = await this.collectProjectionSourceIds(
|
|
1536
|
+
store,
|
|
1537
|
+
existingDoc.id,
|
|
1538
|
+
projectionSourceIds
|
|
1539
|
+
);
|
|
1540
|
+
if (collectError) {
|
|
1541
|
+
return {
|
|
1542
|
+
result: {
|
|
1543
|
+
relPath,
|
|
1544
|
+
status: "error",
|
|
1545
|
+
errorCode: collectError.code,
|
|
1546
|
+
errorMessage: collectError.message,
|
|
1547
|
+
},
|
|
1548
|
+
markedInactive: 0,
|
|
1549
|
+
};
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
for (const recordDocument of recordDocuments) {
|
|
1553
|
+
const collectError = await this.collectProjectionSourceIds(
|
|
1554
|
+
store,
|
|
1555
|
+
recordDocument.id,
|
|
1556
|
+
projectionSourceIds
|
|
1557
|
+
);
|
|
1558
|
+
if (collectError) {
|
|
1559
|
+
return {
|
|
1560
|
+
result: {
|
|
1561
|
+
relPath,
|
|
1562
|
+
status: "error",
|
|
1563
|
+
errorCode: collectError.code,
|
|
1564
|
+
errorMessage: collectError.message,
|
|
1565
|
+
},
|
|
1566
|
+
markedInactive: 0,
|
|
1567
|
+
};
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1368
1571
|
|
|
1572
|
+
const docs = recordDocuments ?? [];
|
|
1573
|
+
// Physical source path may own multiple logical docs (record containers).
|
|
1574
|
+
// Deduplicate while preserving the primary source path first.
|
|
1575
|
+
const activePathSet = new Set<string>();
|
|
1576
|
+
if (existingDoc?.active) {
|
|
1577
|
+
activePathSet.add(relPath);
|
|
1578
|
+
}
|
|
1579
|
+
for (const document of docs) {
|
|
1580
|
+
if (document.active) {
|
|
1581
|
+
activePathSet.add(document.relPath);
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
const activePaths = [...activePathSet];
|
|
1585
|
+
if (activePaths.length > 0) {
|
|
1586
|
+
const inactiveResult = await store.markInactive(
|
|
1587
|
+
collection.name,
|
|
1588
|
+
activePaths
|
|
1589
|
+
);
|
|
1590
|
+
if (!inactiveResult.ok) {
|
|
1591
|
+
return {
|
|
1592
|
+
result: {
|
|
1593
|
+
relPath,
|
|
1594
|
+
status: "error",
|
|
1595
|
+
errorCode: inactiveResult.error.code,
|
|
1596
|
+
errorMessage: inactiveResult.error.message,
|
|
1597
|
+
},
|
|
1598
|
+
markedInactive: 0,
|
|
1599
|
+
};
|
|
1600
|
+
}
|
|
1601
|
+
return {
|
|
1602
|
+
result: {
|
|
1603
|
+
relPath,
|
|
1604
|
+
status: "updated",
|
|
1605
|
+
docid: existingDoc?.docid,
|
|
1606
|
+
},
|
|
1607
|
+
markedInactive: inactiveResult.value,
|
|
1608
|
+
};
|
|
1609
|
+
}
|
|
1369
1610
|
return {
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
filesSkipped: skipped,
|
|
1377
|
-
filesMarkedInactive: markedInactive,
|
|
1378
|
-
durationMs: Date.now() - startedAt,
|
|
1379
|
-
files: results,
|
|
1380
|
-
errors,
|
|
1611
|
+
result: {
|
|
1612
|
+
relPath,
|
|
1613
|
+
status: existingDoc ? "unchanged" : "skipped",
|
|
1614
|
+
docid: existingDoc?.docid,
|
|
1615
|
+
},
|
|
1616
|
+
markedInactive: 0,
|
|
1381
1617
|
};
|
|
1382
1618
|
}
|
|
1383
1619
|
|
|
1620
|
+
/**
|
|
1621
|
+
* Collect source doc ids that need typed-edge re-projection after a change.
|
|
1622
|
+
* Backlink lookup failures are fatal for the caller (not silently ignored).
|
|
1623
|
+
*/
|
|
1384
1624
|
private async collectProjectionSourceIds(
|
|
1385
1625
|
store: StorePort,
|
|
1386
1626
|
documentId: number,
|
|
1387
1627
|
sourceIds: Set<number>
|
|
1388
|
-
): Promise<
|
|
1628
|
+
): Promise<{ code: string; message: string } | null> {
|
|
1389
1629
|
sourceIds.add(documentId);
|
|
1390
1630
|
const [linkBacklinks, edgeBacklinks] = await Promise.all([
|
|
1391
1631
|
store.getBacklinksForDoc(documentId),
|
|
1392
1632
|
store.getEdgeBacklinksForDoc(documentId),
|
|
1393
1633
|
]);
|
|
1394
|
-
if (linkBacklinks.ok) {
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1634
|
+
if (!linkBacklinks.ok) {
|
|
1635
|
+
return {
|
|
1636
|
+
code: linkBacklinks.error.code,
|
|
1637
|
+
message: linkBacklinks.error.message,
|
|
1638
|
+
};
|
|
1398
1639
|
}
|
|
1399
|
-
if (edgeBacklinks.ok) {
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1640
|
+
if (!edgeBacklinks.ok) {
|
|
1641
|
+
return {
|
|
1642
|
+
code: edgeBacklinks.error.code,
|
|
1643
|
+
message: edgeBacklinks.error.message,
|
|
1644
|
+
};
|
|
1645
|
+
}
|
|
1646
|
+
for (const backlink of linkBacklinks.value) {
|
|
1647
|
+
sourceIds.add(backlink.sourceDocId);
|
|
1648
|
+
}
|
|
1649
|
+
for (const backlink of edgeBacklinks.value) {
|
|
1650
|
+
sourceIds.add(backlink.sourceDocId);
|
|
1403
1651
|
}
|
|
1652
|
+
return null;
|
|
1404
1653
|
}
|
|
1405
1654
|
|
|
1406
1655
|
private async projectTypedEdges(
|
|
@@ -1441,7 +1690,19 @@ export class SyncService {
|
|
|
1441
1690
|
if (selectedSourceIds) {
|
|
1442
1691
|
for (const documentId of selectedSourceIds) {
|
|
1443
1692
|
if (!activeIds.has(documentId)) {
|
|
1444
|
-
|
|
1693
|
+
// Clear typed edges for inactivated projection sources.
|
|
1694
|
+
const clearResult = await store.setDocEdges(
|
|
1695
|
+
documentId,
|
|
1696
|
+
[],
|
|
1697
|
+
"frontmatter-relation"
|
|
1698
|
+
);
|
|
1699
|
+
if (!clearResult.ok) {
|
|
1700
|
+
errors.push({
|
|
1701
|
+
relPath: `(doc:${documentId})`,
|
|
1702
|
+
code: clearResult.error.code,
|
|
1703
|
+
message: clearResult.error.message,
|
|
1704
|
+
});
|
|
1705
|
+
}
|
|
1445
1706
|
}
|
|
1446
1707
|
}
|
|
1447
1708
|
}
|
|
@@ -1798,10 +2059,18 @@ export class SyncService {
|
|
|
1798
2059
|
}
|
|
1799
2060
|
}
|
|
1800
2061
|
|
|
1801
|
-
// 5. Mark missing files as inactive
|
|
2062
|
+
// 5. Mark missing files as inactive (inventory failures are hard errors).
|
|
1802
2063
|
let markedInactive = 0;
|
|
2064
|
+
let inventoryErrored = 0;
|
|
1803
2065
|
const existingDocsResult = await store.listDocuments(collection.name);
|
|
1804
|
-
if (existingDocsResult.ok) {
|
|
2066
|
+
if (!existingDocsResult.ok) {
|
|
2067
|
+
inventoryErrored += 1;
|
|
2068
|
+
errors.push({
|
|
2069
|
+
relPath: "",
|
|
2070
|
+
code: existingDocsResult.error.code,
|
|
2071
|
+
message: existingDocsResult.error.message,
|
|
2072
|
+
});
|
|
2073
|
+
} else {
|
|
1805
2074
|
const missingPaths = existingDocsResult.value
|
|
1806
2075
|
.filter(
|
|
1807
2076
|
(document) =>
|
|
@@ -1817,6 +2086,21 @@ export class SyncService {
|
|
|
1817
2086
|
);
|
|
1818
2087
|
if (markResult.ok) {
|
|
1819
2088
|
markedInactive = markResult.value;
|
|
2089
|
+
} else {
|
|
2090
|
+
inventoryErrored += missingPaths.length;
|
|
2091
|
+
errors.push({
|
|
2092
|
+
relPath: missingPaths[0] ?? "",
|
|
2093
|
+
code: markResult.error.code,
|
|
2094
|
+
message: markResult.error.message,
|
|
2095
|
+
});
|
|
2096
|
+
for (const missingPath of missingPaths) {
|
|
2097
|
+
fileResults.push({
|
|
2098
|
+
relPath: missingPath,
|
|
2099
|
+
status: "error",
|
|
2100
|
+
errorCode: markResult.error.code,
|
|
2101
|
+
errorMessage: markResult.error.message,
|
|
2102
|
+
});
|
|
2103
|
+
}
|
|
1820
2104
|
}
|
|
1821
2105
|
}
|
|
1822
2106
|
}
|
|
@@ -1827,11 +2111,11 @@ export class SyncService {
|
|
|
1827
2111
|
|
|
1828
2112
|
return {
|
|
1829
2113
|
collection: collection.name,
|
|
1830
|
-
filesProcessed: entries.length,
|
|
2114
|
+
filesProcessed: entries.length + inventoryErrored,
|
|
1831
2115
|
filesAdded: added,
|
|
1832
2116
|
filesUpdated: updated,
|
|
1833
2117
|
filesUnchanged: unchanged,
|
|
1834
|
-
filesErrored: errored,
|
|
2118
|
+
filesErrored: errored + inventoryErrored,
|
|
1835
2119
|
filesSkipped: skipped.length + dynamicSkipped,
|
|
1836
2120
|
filesMarkedInactive: markedInactive,
|
|
1837
2121
|
durationMs: Date.now() - startTime,
|