@lmzhen/dsh-evolution-state-json 0.3.71 → 0.3.73

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
@@ -175,7 +175,7 @@ async function ensureCorruptCopy(ctx, io, root, file, bad) {
175
175
  }
176
176
  return wrote;
177
177
  }
178
- async function jsonTransact(ctx, io, root, file, task) {
178
+ async function jsonTransact(ctx, io, root, file, task, options) {
179
179
  await transactIo(io(), join(root, file), async (current) => {
180
180
  let parsed = null;
181
181
  if (current !== null) {
@@ -194,9 +194,14 @@ async function jsonTransact(ctx, io, root, file, task) {
194
194
  for (const [id, record] of Object.entries(parsed)) if (failing.some(([failedId]) => failedId === id)) bad[id] = record;
195
195
  else good[id] = record;
196
196
  reportGateViolation(ctx, file, failing);
197
- if (await ensureCorruptCopy(ctx, io, root, file, bad)) parsed = good;
198
- else ctx.logger.warn(`evolution-state-json: keeping ${failing.length} malformed record(s) in ${file} — the quarantine copy could not be written, so rewriting the file without them would destroy the only copy`);
199
- }
197
+ if (await ensureCorruptCopy(ctx, io, root, file, bad)) {
198
+ parsed = good;
199
+ options?.onGateDrop?.(file, failing.map(([id]) => id));
200
+ } else {
201
+ options?.onGateDrop?.(file, []);
202
+ ctx.logger.warn(`evolution-state-json: keeping ${failing.length} malformed record(s) in ${file} — the quarantine copy could not be written, so rewriting the file without them would destroy the only copy`);
203
+ }
204
+ } else options?.onGateDrop?.(file, []);
200
205
  }
201
206
  const next = await task(parsed);
202
207
  if (next !== null && RECORD_MAP_FILES.has(file) && !isPlainRecord(next)) throw writeGateError(`evolution state file "${file}" task returned ${Array.isArray(next) ? "an array" : typeof next} (expected null or a plain JSON object map of records); not written.`);
@@ -212,6 +217,13 @@ function apply(ctx, rawConfig = {}) {
212
217
  if (rawConfig.root !== void 0 && rawConfig.root.trim() !== "" && !isAbsolute(rawConfig.root)) ctx.logger.warn(`evolution-state-json: config.root "${rawConfig.root}" is a relative path and resolves against the process CWD ("${root}") — pass an absolute path to make the store location launch-independent`);
213
218
  const io = () => ctx.evolutionIo.provider();
214
219
  const pathOf = (file) => join(root, file);
220
+ const gateDroppedCurrent = /* @__PURE__ */ new Set();
221
+ const noteGateDrop = (file, ids) => {
222
+ if (file !== PENDING_STATE_FILE) return;
223
+ gateDroppedCurrent.clear();
224
+ for (const id of ids) gateDroppedCurrent.add(id);
225
+ };
226
+ let warnedPendingCapacity = false;
215
227
  async function readJson(file) {
216
228
  const raw = await io().readText(pathOf(file));
217
229
  if (raw === null) return null;
@@ -232,8 +244,13 @@ function apply(ctx, rawConfig = {}) {
232
244
  else good[id] = record;
233
245
  reportGateViolation(ctx, file, failing);
234
246
  await ensureCorruptCopy(ctx, io, root, file, bad);
247
+ if (file === PENDING_STATE_FILE) {
248
+ gateDroppedCurrent.clear();
249
+ for (const id of Object.keys(bad)) gateDroppedCurrent.add(id);
250
+ }
235
251
  return good;
236
252
  }
253
+ if (file === PENDING_STATE_FILE) gateDroppedCurrent.clear();
237
254
  return parsed;
238
255
  }
239
256
  const mutate = makeSerialQueue();
@@ -265,6 +282,7 @@ function apply(ctx, rawConfig = {}) {
265
282
  for (const [id, record] of Object.entries(legacy)) {
266
283
  if (id in (current ?? {})) continue;
267
284
  if (archivedIds.has(id)) continue;
285
+ if (gateDroppedCurrent.has(id)) continue;
268
286
  retired[id] = record;
269
287
  }
270
288
  return retired;
@@ -279,11 +297,12 @@ function apply(ctx, rawConfig = {}) {
279
297
  for (const [id, record] of Object.entries(legacy)) {
280
298
  if (id in merged) continue;
281
299
  if (archivedIds.has(id)) continue;
300
+ if (gateDroppedCurrent.has(id)) continue;
282
301
  merged[id] = record;
283
302
  retired[id] = record;
284
303
  }
285
304
  return merged;
286
- });
305
+ }, { onGateDrop: noteGateDrop });
287
306
  await io().rename(pathOf(PENDING_LEGACY_FILE), `${pathOf(PENDING_LEGACY_FILE)}.migrated`);
288
307
  legacyMigrated = true;
289
308
  return retired;
@@ -464,10 +483,17 @@ function apply(ctx, rawConfig = {}) {
464
483
  async savePending(record) {
465
484
  await mutate(async () => {
466
485
  await jsonTransact(ctx, io, root, PENDING_STATE_FILE, async (current) => {
467
- return {
486
+ const map = {
468
487
  ...await mergedWithFilteredLegacy(legacyMigrated ? null : await readJson(PENDING_LEGACY_FILE), current ?? {}),
469
488
  [record.id]: record
470
489
  };
490
+ const liveCount = Object.values(map).filter((entry) => entry.status === "pending" || entry.status === "executing").length;
491
+ if (liveCount > PENDING_RESOLVED_CAP$1 && !warnedPendingCapacity) {
492
+ warnedPendingCapacity = true;
493
+ ctx.logger.warn(`evolution-state-json: ${liveCount} pending/executing staged records exceed the resolved cap (${PENDING_RESOLVED_CAP$1}) — they are never trimmed by design; resolve or reject them (/evolution pending) or the file keeps growing`);
494
+ }
495
+ if (liveCount <= PENDING_RESOLVED_CAP$1) warnedPendingCapacity = false;
496
+ return map;
471
497
  });
472
498
  });
473
499
  },
@@ -14,6 +14,8 @@ export interface Config {
14
14
  root?: string;
15
15
  }
16
16
  export declare const Config: z<Config>;
17
- export declare function jsonTransact<T>(ctx: Context, io: () => EvolutionIoLike, root: string, file: string, task: (current: T | null) => T | null | Promise<T | null>): Promise<void>;
17
+ export declare function jsonTransact<T>(ctx: Context, io: () => EvolutionIoLike, root: string, file: string, task: (current: T | null) => T | null | Promise<T | null>, options?: {
18
+ onGateDrop?: (file: string, ids: string[]) => void;
19
+ }): Promise<void>;
18
20
  export declare function apply(ctx: Context, rawConfig?: Config): void;
19
21
  //# sourceMappingURL=index.d.ts.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lmzhen/dsh-evolution-state-json",
3
3
  "description": "JSON-file evolution state provider over the IO seam (community build)",
4
- "version": "0.3.71",
4
+ "version": "0.3.73",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -31,18 +31,18 @@
31
31
  "license": "MIT",
32
32
  "dependencies": {
33
33
  "@deepseek-ai/schemastery": "^3.18.1",
34
- "@lmzhen/dsh-evolution-core": "^0.3.71"
34
+ "@lmzhen/dsh-evolution-core": "^0.3.73"
35
35
  },
36
36
  "peerDependencies": {
37
37
  "@deepseek-ai/dsh-invariants": "^0.1.5-rc.2",
38
38
  "@deepseek-ai/cordis": "^4.0.1",
39
- "@lmzhen/dsh-evolution-io": "^0.3.71",
40
- "@lmzhen/dsh-evolution-state-storage": "^0.3.71"
39
+ "@lmzhen/dsh-evolution-io": "^0.3.73",
40
+ "@lmzhen/dsh-evolution-state-storage": "^0.3.73"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@deepseek-ai/dsh-invariants": "^0.1.5-rc.2",
44
- "@lmzhen/dsh-evolution-io": "^0.3.71",
45
- "@lmzhen/dsh-evolution-state-storage": "^0.3.71",
46
- "@lmzhen/dsh-evolution-io-node": "^0.3.71"
44
+ "@lmzhen/dsh-evolution-io": "^0.3.73",
45
+ "@lmzhen/dsh-evolution-state-storage": "^0.3.73",
46
+ "@lmzhen/dsh-evolution-io-node": "^0.3.73"
47
47
  }
48
48
  }