@lmzhen/dsh-evolution-state-json 0.3.32 → 0.3.34

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.
Files changed (2) hide show
  1. package/lib/index.js +47 -29
  2. package/package.json +6 -6
package/lib/index.js CHANGED
@@ -95,25 +95,35 @@ function apply(ctx, rawConfig) {
95
95
  }
96
96
  const mutate = makeSerialQueue();
97
97
  let legacyMigrated = false;
98
+ let archivedIdsCache = null;
99
+ async function readArchivedIds() {
100
+ if (archivedIdsCache !== null) return archivedIdsCache;
101
+ const ids = /* @__PURE__ */ new Set();
102
+ try {
103
+ const rawArchive = await readJson("pending-state-archive.json");
104
+ if (Array.isArray(rawArchive)) {
105
+ for (const entry of rawArchive) if (entry && typeof entry.id === "string") ids.add(entry.id);
106
+ }
107
+ } catch {}
108
+ archivedIdsCache = ids;
109
+ return ids;
110
+ }
111
+ function filterLegacy(legacy, current, archivedIds) {
112
+ const retired = {};
113
+ for (const [id, record] of Object.entries(legacy)) {
114
+ if (id in (current ?? {})) continue;
115
+ if (archivedIds.has(id)) continue;
116
+ retired[id] = record;
117
+ }
118
+ return retired;
119
+ }
98
120
  async function retireLegacyOnce(legacy, current) {
99
121
  if (legacyMigrated) return {};
100
122
  try {
101
- const archivedIds = /* @__PURE__ */ new Set();
102
- try {
103
- const rawArchive = await readJson("pending-state-archive.json");
104
- if (Array.isArray(rawArchive)) {
105
- for (const entry of rawArchive) if (entry && typeof entry.id === "string") archivedIds.add(entry.id);
106
- }
107
- } catch {}
108
- const retired = {};
109
- for (const [id, record] of Object.entries(legacy)) {
110
- if (id in (current ?? {})) continue;
111
- if (archivedIds.has(id)) continue;
112
- retired[id] = record;
113
- }
114
- await jsonTransact(io, root, "pending-state.json", () => ({
123
+ const retired = filterLegacy(legacy, current, await readArchivedIds());
124
+ await jsonTransact(io, root, "pending-state.json", (fresh) => ({
115
125
  ...retired,
116
- ...current ?? {}
126
+ ...fresh ?? {}
117
127
  }));
118
128
  await io().rename(pathOf("pending.json"), pathOf("pending.json.migrated"));
119
129
  legacyMigrated = true;
@@ -130,6 +140,17 @@ function apply(ctx, rawConfig) {
130
140
  };
131
141
  return { ...current ?? {} };
132
142
  }
143
+ /** V6-01 (0.3.34): single-sourced legacy merge for the four mutation paths —
144
+ * the SAME exclusion as the retirement read path, so a mutation that runs
145
+ * before any retirement cannot fixate a ghost pending twin in current
146
+ * (the archive id set is cached once; current-wins covers stale misses). */
147
+ async function mergedWithFilteredLegacy(legacy, current) {
148
+ if (legacy === null) return current;
149
+ return {
150
+ ...filterLegacy(legacy, current, await readArchivedIds()),
151
+ ...current
152
+ };
153
+ }
133
154
  /** 0.3.22 (F-336): when the live pending map holds more than
134
155
  * `PENDING_RESOLVED_CAP` resolved records, drop the oldest (by resolvedAt,
135
156
  * then insertion order on ties) from the map and return them for archiving.
@@ -175,6 +196,13 @@ function apply(ctx, rawConfig) {
175
196
  const parsed = JSON.parse(current);
176
197
  if (Array.isArray(parsed)) archive = parsed;
177
198
  } catch {}
199
+ const archiveKeys = /* @__PURE__ */ new Set();
200
+ archive = archive.filter((entry) => {
201
+ const key = pendingArchiveKey(entry);
202
+ if (archiveKeys.has(key)) return false;
203
+ archiveKeys.add(key);
204
+ return true;
205
+ });
178
206
  const seen = new Set(archive.map(pendingArchiveKey));
179
207
  const fresh = records.filter((record) => !seen.has(pendingArchiveKey(record)));
180
208
  if (fresh.length === 0) return current;
@@ -237,8 +265,7 @@ function apply(ctx, rawConfig) {
237
265
  await mutate(async () => {
238
266
  await jsonTransact(io, root, "pending-state.json", async (current) => {
239
267
  return {
240
- ...(legacyMigrated ? null : await readJson("pending.json")) ?? {},
241
- ...current ?? {},
268
+ ...await mergedWithFilteredLegacy(legacyMigrated ? null : await readJson("pending.json"), current ?? {}),
242
269
  [record.id]: record
243
270
  };
244
271
  });
@@ -248,10 +275,7 @@ function apply(ctx, rawConfig) {
248
275
  return await mutate(async () => {
249
276
  const slot = { claimed: null };
250
277
  await jsonTransact(io, root, "pending-state.json", async (current) => {
251
- const map = {
252
- ...(legacyMigrated ? null : await readJson("pending.json")) ?? {},
253
- ...current ?? {}
254
- };
278
+ const map = { ...await mergedWithFilteredLegacy(legacyMigrated ? null : await readJson("pending.json"), current ?? {}) };
255
279
  const record = map[id] ?? null;
256
280
  if (record === null || !canClaimPending(record.status)) return map;
257
281
  const now = Date.now();
@@ -270,10 +294,7 @@ function apply(ctx, rawConfig) {
270
294
  async releasePendingClaim(id, claimId) {
271
295
  await mutate(async () => {
272
296
  await jsonTransact(io, root, "pending-state.json", async (current) => {
273
- const map = {
274
- ...(legacyMigrated ? null : await readJson("pending.json")) ?? {},
275
- ...current ?? {}
276
- };
297
+ const map = { ...await mergedWithFilteredLegacy(legacyMigrated ? null : await readJson("pending.json"), current ?? {}) };
277
298
  const record = map[id];
278
299
  if (!record || record.claimedBy !== claimId) return map;
279
300
  record.status = releasedStatus(record.status);
@@ -291,10 +312,7 @@ function apply(ctx, rawConfig) {
291
312
  };
292
313
  let evicted = [];
293
314
  await jsonTransact(io, root, "pending-state.json", async (current) => {
294
- const map = {
295
- ...(legacyMigrated ? null : await readJson("pending.json")) ?? {},
296
- ...current ?? {}
297
- };
315
+ const map = { ...await mergedWithFilteredLegacy(legacyMigrated ? null : await readJson("pending.json"), current ?? {}) };
298
316
  const record = map[id] ?? null;
299
317
  if (record === null || !canResolvePending(record.status)) {
300
318
  result = {
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.32",
4
+ "version": "0.3.34",
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.32"
34
+ "@lmzhen/dsh-evolution-core": "^0.3.34"
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.32",
40
- "@lmzhen/dsh-evolution-state-storage": "^0.3.32"
39
+ "@lmzhen/dsh-evolution-io": "^0.3.34",
40
+ "@lmzhen/dsh-evolution-state-storage": "^0.3.34"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
44
- "@lmzhen/dsh-evolution-io": "^0.3.32",
45
- "@lmzhen/dsh-evolution-state-storage": "^0.3.32"
44
+ "@lmzhen/dsh-evolution-io": "^0.3.34",
45
+ "@lmzhen/dsh-evolution-state-storage": "^0.3.34"
46
46
  }
47
47
  }