@lmzhen/dsh-evolution-state-json 0.3.36 → 0.3.38

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
@@ -55,11 +55,12 @@ async function quarantine(io, root, file, raw, reason) {
55
55
  * Cross-process JSON-file RMW (v3-audit M-8): every read-modify-write state
56
56
  * mutation runs inside the IO backend's transact lock (via transactIo) so a
57
57
  * second process sharing DSH_HOME cannot interleave its claim/resolve.
58
- * `task` returns the next value (null = keep); for a record-map file the
59
- * return must be null or a plain object map of records, and an array/scalar
60
- * would be persisted as a corrupt map so it fails loud before any write
61
- * (0.3.28, V4-08). The legacy `pending.json` merge stays inside the task via
62
- * `readJson` where relevant.
58
+ * `task` returns the next value; null = ENSURE-ABSENT (the file is deleted
59
+ * when it exists the parity of the transact seam, V6-32). For a record-map
60
+ * file the return must be null or a plain object map of records, and an
61
+ * array/scalar would be persisted as a corrupt map so it fails loud before
62
+ * any write (0.3.28, V4-08). The legacy `pending.json` merge stays inside the
63
+ * task via `readJson` where relevant.
63
64
  */
64
65
  async function jsonTransact(io, root, file, task) {
65
66
  await transactIo(io(), join(root, file), async (current) => {
@@ -104,6 +105,12 @@ function apply(ctx, rawConfig) {
104
105
  if (Array.isArray(rawArchive)) {
105
106
  for (const entry of rawArchive) if (entry && typeof entry.id === "string") ids.add(entry.id);
106
107
  }
108
+ try {
109
+ const rawBak = await readJson("pending-state-archive.json.bak");
110
+ if (Array.isArray(rawBak)) {
111
+ for (const entry of rawBak) if (entry && typeof entry.id === "string") ids.add(entry.id);
112
+ }
113
+ } catch {}
107
114
  } catch {}
108
115
  archivedIdsCache = ids;
109
116
  return ids;
@@ -170,9 +177,10 @@ function apply(ctx, rawConfig) {
170
177
  return Number.isNaN(parsed) ? Number.MAX_SAFE_INTEGER : parsed;
171
178
  };
172
179
  const oldest = resolved.sort((a, b) => entryTime(a) - entryTime(b)).slice(0, overflow);
173
- const evictIds = new Set(oldest.map((record) => record.id));
180
+ const oldestKeys = /* @__PURE__ */ new Set();
181
+ for (const [key, value] of Object.entries(map)) if (oldest.includes(value)) oldestKeys.add(key);
174
182
  const kept = {};
175
- for (const [key, value] of Object.entries(map)) if (!evictIds.has(value.id)) kept[key] = value;
183
+ for (const [key, value] of Object.entries(map)) if (!oldestKeys.has(key)) kept[key] = value;
176
184
  return {
177
185
  map: kept,
178
186
  evicted: oldest
@@ -197,19 +205,21 @@ function apply(ctx, rawConfig) {
197
205
  if (Array.isArray(parsed)) archive = parsed;
198
206
  } catch {}
199
207
  const archiveKeys = /* @__PURE__ */ new Set();
200
- archive = archive.filter((entry) => {
208
+ const collapsed = archive.filter((entry) => {
201
209
  const key = pendingArchiveKey(entry);
202
210
  if (archiveKeys.has(key)) return false;
203
211
  archiveKeys.add(key);
204
212
  return true;
205
213
  });
214
+ const hadDuplicates = collapsed.length !== archive.length;
215
+ archive = collapsed;
206
216
  const seen = new Set(archive.map(pendingArchiveKey));
207
217
  const fresh = records.filter((record) => !seen.has(pendingArchiveKey(record)));
208
- if (fresh.length === 0) return current;
218
+ if (fresh.length === 0 && !hadDuplicates) return current;
209
219
  const next = [...archive, ...fresh];
210
220
  if (next.length > ARCHIVE_RESOLVED_CAP) {
211
221
  if (archive.length > 0) await io().writeText(pathOf("pending-state-archive.json.bak"), JSON.stringify(archive, null, 2)).catch(() => {});
212
- return JSON.stringify(fresh.slice(-5e3), null, 2);
222
+ return JSON.stringify((fresh.length > 0 ? fresh : next).slice(-5e3), null, 2);
213
223
  }
214
224
  return JSON.stringify(next, null, 2);
215
225
  });
@@ -18,11 +18,12 @@ export declare const Config: z<Config>;
18
18
  * Cross-process JSON-file RMW (v3-audit M-8): every read-modify-write state
19
19
  * mutation runs inside the IO backend's transact lock (via transactIo) so a
20
20
  * second process sharing DSH_HOME cannot interleave its claim/resolve.
21
- * `task` returns the next value (null = keep); for a record-map file the
22
- * return must be null or a plain object map of records, and an array/scalar
23
- * would be persisted as a corrupt map so it fails loud before any write
24
- * (0.3.28, V4-08). The legacy `pending.json` merge stays inside the task via
25
- * `readJson` where relevant.
21
+ * `task` returns the next value; null = ENSURE-ABSENT (the file is deleted
22
+ * when it exists the parity of the transact seam, V6-32). For a record-map
23
+ * file the return must be null or a plain object map of records, and an
24
+ * array/scalar would be persisted as a corrupt map so it fails loud before
25
+ * any write (0.3.28, V4-08). The legacy `pending.json` merge stays inside the
26
+ * task via `readJson` where relevant.
26
27
  */
27
28
  export declare function jsonTransact<T>(io: () => EvolutionIoLike, root: string, file: string, task: (current: T | null) => T | null | Promise<T | null>): Promise<void>;
28
29
  export declare function apply(ctx: Context, rawConfig: Config): void;
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.36",
4
+ "version": "0.3.38",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -31,17 +31,17 @@
31
31
  "license": "MIT",
32
32
  "dependencies": {
33
33
  "@deepseek-ai/schemastery": "^3.18.1",
34
- "@lmzhen/dsh-evolution-core": "^0.3.36"
34
+ "@lmzhen/dsh-evolution-core": "^0.3.38"
35
35
  },
36
36
  "peerDependencies": {
37
37
  "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
38
38
  "@deepseek-ai/cordis": "^4.0.1",
39
- "@lmzhen/dsh-evolution-io": "^0.3.36",
40
- "@lmzhen/dsh-evolution-state-storage": "^0.3.36"
39
+ "@lmzhen/dsh-evolution-io": "^0.3.38",
40
+ "@lmzhen/dsh-evolution-state-storage": "^0.3.38"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
44
- "@lmzhen/dsh-evolution-io": "^0.3.36",
45
- "@lmzhen/dsh-evolution-state-storage": "^0.3.36"
44
+ "@lmzhen/dsh-evolution-io": "^0.3.38",
45
+ "@lmzhen/dsh-evolution-state-storage": "^0.3.38"
46
46
  }
47
47
  }