@lmzhen/dsh-evolution-merge 0.30.11 → 0.30.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/lib/index.js CHANGED
@@ -345,10 +345,17 @@ async function mutateMergeState(io, home, now, fn) {
345
345
  });
346
346
  return result;
347
347
  }
348
- async function upsertRow(io, home, now, row, expect = "any") {
348
+ async function upsertRow(io, home, now, row, expect = "any", cas) {
349
349
  return await mutateMergeState(io, home, now, (rows) => {
350
350
  const index = rows.findIndex((item) => item.pairKey === row.pairKey);
351
351
  const current = index < 0 ? void 0 : rows[index];
352
+ if (cas !== void 0 && (cas === null && current !== void 0 || cas !== null && current?.updatedAt !== cas)) return {
353
+ rows,
354
+ result: {
355
+ ok: false,
356
+ current: current ?? row
357
+ }
358
+ };
352
359
  if (current !== void 0 && expect !== "any" && (expect === "absent" || queueOf(current.state) !== expect)) return {
353
360
  rows,
354
361
  result: {
@@ -1790,16 +1797,16 @@ async function runScan(options) {
1790
1797
  });
1791
1798
  if (!created.ok || created.row === null) throw new Error(created.ok ? "candidate row missing" : created.message);
1792
1799
  row = created.row;
1793
- const placed = await upsertRow(options.io, options.home, options.now, row, "verify");
1794
- if (!placed.ok && placed.current.state === "skipped") {
1795
- const restored = transition(placed.current, {
1800
+ const before = (await loadMergeState(options.io, options.home)).state.rows.find((item) => item.pairKey === row.pairKey);
1801
+ const placed = await upsertRow(options.io, options.home, options.now, row, "verify", before?.updatedAt ?? null);
1802
+ if (!placed.ok && before?.state === "skipped") {
1803
+ const restored = transition(before, {
1796
1804
  type: "restore",
1797
1805
  at: at(options.now)
1798
1806
  });
1799
1807
  if (!restored.ok || restored.row === null) throw new Error("cannot restore skipped pair");
1808
+ if (!(await upsertRow(options.io, options.home, options.now, restored.row, "any", before.updatedAt)).ok) throw new Error("pair changed while restoring skipped row");
1800
1809
  row = restored.row;
1801
- const retried = await upsertRow(options.io, options.home, options.now, row, "verify");
1802
- if (!retried.ok) throw new Error("pair already in " + retried.current.state);
1803
1810
  } else if (!placed.ok) throw new Error("pair already in " + placed.current.state);
1804
1811
  } catch (error) {
1805
1812
  totals.failed += 1;
@@ -65,15 +65,16 @@ export async function runScan(options) {
65
65
  if (!created.ok || created.row === null)
66
66
  throw new Error(created.ok ? 'candidate row missing' : created.message);
67
67
  row = created.row;
68
- const placed = await upsertRow(options.io, options.home, options.now, row, 'verify');
69
- if (!placed.ok && placed.current.state === 'skipped') {
70
- const restored = transition(placed.current, { type: 'restore', at: at(options.now) });
68
+ const before = (await loadMergeState(options.io, options.home)).state.rows.find(item => item.pairKey === row.pairKey);
69
+ const placed = await upsertRow(options.io, options.home, options.now, row, 'verify', before?.updatedAt ?? null);
70
+ if (!placed.ok && before?.state === 'skipped') {
71
+ const restored = transition(before, { type: 'restore', at: at(options.now) });
71
72
  if (!restored.ok || restored.row === null)
72
73
  throw new Error('cannot restore skipped pair');
74
+ const persisted = await upsertRow(options.io, options.home, options.now, restored.row, 'any', before.updatedAt);
75
+ if (!persisted.ok)
76
+ throw new Error('pair changed while restoring skipped row');
73
77
  row = restored.row;
74
- const retried = await upsertRow(options.io, options.home, options.now, row, 'verify');
75
- if (!retried.ok)
76
- throw new Error('pair already in ' + retried.current.state);
77
78
  }
78
79
  else if (!placed.ok) {
79
80
  throw new Error('pair already in ' + placed.current.state);
@@ -29,7 +29,7 @@ export type UpsertResult = {
29
29
  readonly ok: false;
30
30
  readonly current: MergeRow;
31
31
  };
32
- export declare function upsertRow(io: EvolutionIoLike, home: string, now: () => number, row: MergeRow, expect?: UpsertExpect): Promise<UpsertResult>;
32
+ export declare function upsertRow(io: EvolutionIoLike, home: string, now: () => number, row: MergeRow, expect?: UpsertExpect, cas?: string | null): Promise<UpsertResult>;
33
33
  export declare function moveRow(io: EvolutionIoLike, home: string, now: () => number, id: string, patch: MoveRowPatch): Promise<MergeRow | undefined>;
34
34
  export declare function dropRow(io: EvolutionIoLike, home: string, now: () => number, id: string): Promise<boolean>;
35
35
  export declare function rowsOf(io: EvolutionIoLike, home: string, queue?: MergeQueue): Promise<readonly MergeRow[]>;
@@ -55,10 +55,13 @@ export async function mutateMergeState(io, home, now, fn) {
55
55
  void now;
56
56
  return result;
57
57
  }
58
- export async function upsertRow(io, home, now, row, expect = 'any') {
58
+ export async function upsertRow(io, home, now, row, expect = 'any', cas) {
59
59
  return await mutateMergeState(io, home, now, (rows) => {
60
60
  const index = rows.findIndex(item => item.pairKey === row.pairKey);
61
61
  const current = index < 0 ? undefined : rows[index];
62
+ if (cas !== undefined && ((cas === null && current !== undefined) || (cas !== null && current?.updatedAt !== cas))) {
63
+ return { rows, result: { ok: false, current: current ?? row } };
64
+ }
62
65
  if (current !== undefined && expect !== 'any' && (expect === 'absent' || queueOf(current.state) !== expect)) {
63
66
  return { rows, result: { ok: false, current } };
64
67
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lmzhen/dsh-evolution-merge",
3
3
  "description": "Skill auto-merge domain: screening, two-round authoring, queues and commit orchestration (community build)",
4
- "version": "0.30.11",
4
+ "version": "0.30.12",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -27,8 +27,8 @@
27
27
  "license": "MIT",
28
28
  "dependencies": {
29
29
  "@deepseek-ai/schemastery": "^3.18.5",
30
- "@lmzhen/dsh-evolution-core": "^0.30.11",
31
- "@lmzhen/dsh-evolution-merge-engine": "^0.30.11"
30
+ "@lmzhen/dsh-evolution-core": "^0.30.12",
31
+ "@lmzhen/dsh-evolution-merge-engine": "^0.30.12"
32
32
  },
33
33
  "peerDependencies": {
34
34
  "@deepseek-ai/cordis": "^4.0.5"