dsh-shared-kb 1.0.0 → 1.0.1

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/lib/store/docs.js CHANGED
@@ -376,12 +376,20 @@ export async function writeDoc(params) {
376
376
 
377
377
  // 清单 CAS 写入:rev 对不上会把上面写好的正文留在磁盘上(幂等,下次重跑会覆盖),
378
378
  // 但绝不更新清单,读者看不到半成品。
379
+ //
380
+ // 关键:去重必须在锁内再判一次。锁外那次 findDoc 只是"快速路径",
381
+ // 两个进程同时导入同一份文件时会双双落空,然后都进锁写入 —— 数据不会错
382
+ // (内容相同),但会白白推高 rev、重复触发索引。锁内命中就直接报 unchanged,
383
+ // 且不递增 rev。
379
384
  const result = await withManifest(
380
385
  dir,
381
386
  kb,
382
387
  (manifest) => {
383
388
  const list = manifest.docs ?? (manifest.docs = []);
384
389
  const at = list.findIndex((doc) => doc.id === docId);
390
+ if (at >= 0 && list[at].contentHash === contentHash && !params.force) {
391
+ return { __skip: true, result: { status: "unchanged", version: list[at].version } };
392
+ }
385
393
  if (at >= 0) list[at] = entry;
386
394
  else list.push(entry);
387
395
  return entry;
@@ -400,6 +408,19 @@ export async function writeDoc(params) {
400
408
  chars: entry.chars,
401
409
  });
402
410
 
411
+ // 锁内判重命中:这一轮什么都没改,如实报 unchanged
412
+ if (result.skipped === true) {
413
+ return {
414
+ docId,
415
+ status: "unchanged",
416
+ rev: null,
417
+ version: result.result?.version ?? entry.version,
418
+ title: entry.title,
419
+ parts: entry.parts,
420
+ chars: entry.chars,
421
+ };
422
+ }
423
+
403
424
  return {
404
425
  docId,
405
426
  status: existing ? "updated" : "created",
@@ -164,11 +164,17 @@ export async function withManifest(dir, name, mutate, options = {}) {
164
164
  }
165
165
  const by = options.by ?? { machine: machineId(), pid: process.pid };
166
166
  const now = new Date().toISOString();
167
- const result = await mutate(manifest, { now, by });
167
+ const outcome = await mutate(manifest, { now, by });
168
+ // mutate 可以返回 { __skip: true, result } 表示"读完发现不需要改"。
169
+ // 这种轮次绝不能递增 rev:rev 是"清单内容变了几次"的计数,
170
+ // 空轮递增会让所有人都以为有人在改,也会白白触发索引重建。
171
+ if (outcome && outcome.__skip === true) {
172
+ return { rev: manifest.rev, result: outcome.result ?? null, manifest, skipped: true };
173
+ }
168
174
  manifest.rev = manifest.rev + 1;
169
175
  touch(manifest, by);
170
176
  await writeManifestAtomic(dir, manifest);
171
- return { rev: manifest.rev, result, manifest };
177
+ return { rev: manifest.rev, result: outcome, manifest, skipped: false };
172
178
  },
173
179
  { timeoutMs: options.timeoutMs }
174
180
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-shared-kb",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "DSH 知识库中枢:指定物理地址后每库独立子文件夹落地原始资料与派生分块,自带 SQLite FTS5 全文索引 + 向量语义检索,用租约与 revision CAS 支持多人多机同时读、写入与索引重建",
5
5
  "keywords": [
6
6
  "dsh",