@lmzhen/dsh-evolution-state-storage 0.3.62 → 0.3.64

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
@@ -50,17 +50,46 @@ const canResolvePending = (status) => status === "pending" || status === "execut
50
50
  /** Releasing a claim on an executing record rolls it back to pending (a
51
51
  * runner FAILURE is retryable); other statuses pass through unchanged. */
52
52
  const releasedStatus = (status) => status === "executing" ? "pending" : status;
53
+ /** P2-4 (v15): the live pending map/table is BOUNDED on the RESOLVE path —
54
+ * `tryResolvePending` drops the oldest resolved (approved/rejected) records by
55
+ * `resolvedAt` once more than this many exist. Single source (the v15 audit
56
+ * found the bound was json-only, so domain deployments grew the table without
57
+ * bound).
58
+ *
59
+ * C-6 (v18) contract precision: a direct `savePending` of an already-resolved
60
+ * record does NOT trigger eviction (the cap is maintained by the resolve
61
+ * operation, not by the writer), and pending/executing records are never
62
+ * trimmed. Callers that write resolved audit records themselves own that
63
+ * growth; the seam's resolve path is what keeps the table bounded.
64
+ * The audit ARCHIVE sidecar that json maintains beyond the cap stays
65
+ * json-specific (domain has no sidecar facility) — declared in both READMEs. */
66
+ const PENDING_RESOLVED_CAP = 200;
53
67
  var EvolutionStateStorageRegistry = class extends Service {
54
68
  providers = /* @__PURE__ */ new Map();
69
+ /** C-7 (v18): per-name dispose, mirroring the evolution-io registry. */
70
+ disposals = /* @__PURE__ */ new Map();
55
71
  constructor(ctx) {
56
72
  super(ctx, "evolutionStateStorage");
57
73
  }
74
+ /** C-7 (v18): re-registering the IDENTICAL provider object is idempotent and
75
+ * returns the original dispose (HMR / re-mounted row); a DIFFERENT object
76
+ * under a registered name still fails loud. The dispose carries a generation
77
+ * guard so a stale handle cannot remove a newer registration. */
58
78
  registerProvider(provider) {
59
- if (this.providers.has(provider.name)) throw new Error(`evolution state storage provider "${provider.name}" already registered`);
60
- this.providers.set(provider.name, provider);
61
- return () => {
62
- if (this.providers.get(provider.name) === provider) this.providers.delete(provider.name);
79
+ const idempotent = this.providers.get(provider.name) === provider;
80
+ if (!idempotent && this.providers.has(provider.name)) throw new Error(`evolution state storage provider "${provider.name}" already registered`);
81
+ if (idempotent) {
82
+ const existing = this.disposals.get(provider.name);
83
+ if (existing !== void 0) return existing;
84
+ }
85
+ const dispose = () => {
86
+ if (this.disposals.get(provider.name) !== dispose) return;
87
+ this.providers.delete(provider.name);
88
+ this.disposals.delete(provider.name);
63
89
  };
90
+ this.providers.set(provider.name, provider);
91
+ this.disposals.set(provider.name, dispose);
92
+ return dispose;
64
93
  }
65
94
  /** S-07: whether ANY provider is registered. Lets the state
66
95
  * consumer precheck a pinned `provider` config at mount time (a typo fails
@@ -81,4 +110,4 @@ var EvolutionStateStorageRegistry = class extends Service {
81
110
  }
82
111
  };
83
112
  //#endregion
84
- export { CURATOR_STATE_FILE, CURATOR_STATE_KEY, CURATOR_STATE_TABLE, EvolutionStateStorageRegistry, EvolutionStateStorageRegistry as default, PENDING_ARCHIVE_BAK_FILE, PENDING_ARCHIVE_FILE, PENDING_LEGACY_FILE, PENDING_STATE_FILE, PENDING_TABLE, PROVIDER_DOMAIN, PROVIDER_JSON, REVIEW_STATE_FILE, REVIEW_STATE_TABLE, canClaimPending, canResolvePending, releasedStatus };
113
+ export { CURATOR_STATE_FILE, CURATOR_STATE_KEY, CURATOR_STATE_TABLE, EvolutionStateStorageRegistry, EvolutionStateStorageRegistry as default, PENDING_ARCHIVE_BAK_FILE, PENDING_ARCHIVE_FILE, PENDING_LEGACY_FILE, PENDING_RESOLVED_CAP, PENDING_STATE_FILE, PENDING_TABLE, PROVIDER_DOMAIN, PROVIDER_JSON, REVIEW_STATE_FILE, REVIEW_STATE_TABLE, canClaimPending, canResolvePending, releasedStatus };
@@ -24,6 +24,20 @@ export declare const canResolvePending: (status: PendingStatus) => boolean;
24
24
  /** Releasing a claim on an executing record rolls it back to pending (a
25
25
  * runner FAILURE is retryable); other statuses pass through unchanged. */
26
26
  export declare const releasedStatus: (status: PendingStatus) => PendingStatus;
27
+ /** P2-4 (v15): the live pending map/table is BOUNDED on the RESOLVE path —
28
+ * `tryResolvePending` drops the oldest resolved (approved/rejected) records by
29
+ * `resolvedAt` once more than this many exist. Single source (the v15 audit
30
+ * found the bound was json-only, so domain deployments grew the table without
31
+ * bound).
32
+ *
33
+ * C-6 (v18) contract precision: a direct `savePending` of an already-resolved
34
+ * record does NOT trigger eviction (the cap is maintained by the resolve
35
+ * operation, not by the writer), and pending/executing records are never
36
+ * trimmed. Callers that write resolved audit records themselves own that
37
+ * growth; the seam's resolve path is what keeps the table bounded.
38
+ * The audit ARCHIVE sidecar that json maintains beyond the cap stays
39
+ * json-specific (domain has no sidecar facility) — declared in both READMEs. */
40
+ export declare const PENDING_RESOLVED_CAP = 200;
27
41
  /**
28
42
  * Claim lifecycle (S3.3): pending →(claim)→ executing →(resolve)→ approved/rejected.
29
43
  * release() rolls executing back to pending (failure path). A crash between
@@ -101,7 +115,13 @@ declare module '@deepseek-ai/cordis' {
101
115
  }
102
116
  export declare class EvolutionStateStorageRegistry extends Service {
103
117
  private readonly providers;
118
+ /** C-7 (v18): per-name dispose, mirroring the evolution-io registry. */
119
+ private readonly disposals;
104
120
  constructor(ctx: Context);
121
+ /** C-7 (v18): re-registering the IDENTICAL provider object is idempotent and
122
+ * returns the original dispose (HMR / re-mounted row); a DIFFERENT object
123
+ * under a registered name still fails loud. The dispose carries a generation
124
+ * guard so a stale handle cannot remove a newer registration. */
105
125
  registerProvider(provider: EvolutionStateStorage): () => void;
106
126
  /** S-07: whether ANY provider is registered. Lets the state
107
127
  * consumer precheck a pinned `provider` config at mount time (a typo fails
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lmzhen/dsh-evolution-state-storage",
3
3
  "description": "Provider registry seam for durable evolution state (community build)",
4
- "version": "0.3.62",
4
+ "version": "0.3.64",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },