@deepseek-ai/dsh-session-projection-cache 0.0.1-rc.1
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/LICENSE +28 -0
- package/README.i18n.yaml +6 -0
- package/README.md +62 -0
- package/README.zh.md +62 -0
- package/lib/index.js +286 -0
- package/lib/invariant.js +28 -0
- package/lib/types/index.d.ts +118 -0
- package/lib/types/invariant.d.ts +16 -0
- package/lib/types/spec.d.ts +82 -0
- package/package.json +55 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, DeepSeek
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.i18n.yaml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
|
2
|
+
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
|
+
# after editing either side, bring the other along and re-record with:
|
|
4
|
+
# pnpm run verify-translation-pairing --write packages/session/session-projection-cache/README.md
|
|
5
|
+
README.md: 5d4ad07fab6648acdb40c6aa86d32cc78b4c016e
|
|
6
|
+
README.zh.md: 9dfcd645097cc8390812be3aef82b9535fa953eb
|
package/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# @deepseek-ai/dsh-session-projection-cache
|
|
2
|
+
|
|
3
|
+
English | [中文](README.zh.md)
|
|
4
|
+
|
|
5
|
+
The persisted projection cache (`ctx.sessionProjectionCache`): durable checkpoints of every registered projection unit's state, one record per session on the domain data form (`session_projcache` domain — the shipped json backend lands it beside `workspace.json` under the configured storage root). Design authority: the [session-projection RFC](../../../.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.md) (persisted projection cache section).
|
|
6
|
+
|
|
7
|
+
A stored row `(key → {ver, seq, val})` is a fold shortcut, never an authority: possibly stale (`seq` says exactly how stale) but never wrong. Consequences the implementation commits to:
|
|
8
|
+
|
|
9
|
+
- **Every background write is fail-soft.** A failed durable write logs a warning and keeps the cache stale; the next write or cold read self-heals. A crash between writes costs a longer tail replay, never a wrong value.
|
|
10
|
+
- **A `ver` mismatch against the live unit's `stateVersion` discards, never migrates.** A unit bump invalidates its rows at read time; the key refolds from the log.
|
|
11
|
+
- **Whole-record writes.** Each write replaces the session's full checkpoint (the registry cut is always complete), snapshotted through the lossless-JSON boundary — a unit state violating the plain-JSON contract fails loud.
|
|
12
|
+
- **Records are bound to a log lifecycle, not just an id.** Each record stores the header identity (`createdAt`, `cwd`) it was folded from; every read validates it (the live or stored header is the witness) before accepting a row, so a deleted-then-recreated id or a persistence store swapped under a surviving cache discards the unrelated record instead of seeding phantom values.
|
|
13
|
+
- **The log leads, the cache follows.** A live checkpoint flushes the session's buffered events durably BEFORE the cache row lands, so a crash can leave the cache behind the log (a longer tail replay) but never ahead of it.
|
|
14
|
+
|
|
15
|
+
## Write policy
|
|
16
|
+
|
|
17
|
+
Two mandatory points, throttled in between:
|
|
18
|
+
|
|
19
|
+
| Trigger | Nature |
|
|
20
|
+
|---|---|
|
|
21
|
+
| `turn/end` | Mandatory — the turn-final value is what cold reads want. |
|
|
22
|
+
| Session disposal (detach) | Mandatory — the live-to-cold moment; after it the cold ladder serves this session. |
|
|
23
|
+
| `writeEveryEvents` committed events | Config throttle (count). |
|
|
24
|
+
| `writeIntervalMs` since the first dirty event | Config throttle (interval). |
|
|
25
|
+
|
|
26
|
+
Both `Config` fields are required (no defaults): flush cadence is a deployment choice with no universally correct value, stated in cordis.yml.
|
|
27
|
+
|
|
28
|
+
## Listing read (`cachedSnapshot(meta)`)
|
|
29
|
+
|
|
30
|
+
The zero-I/O rung: whole values viewed straight from the identity-matching stored record (version-matching keys only), returned as a `{asOfSeq, values}` cut — `asOfSeq` is the lowest served-row watermark, so a client seeding its per-session value store under higher-seq-wins can never let a stale list block overwrite a newer push frame. `undefined` when no usable record exists (unknown id, unrelated lifecycle, or no version-matching rows); the api-proxy list carrier turns that into an absent column.
|
|
31
|
+
|
|
32
|
+
## Cold read (`coldSnapshot(id, signal?)`)
|
|
33
|
+
|
|
34
|
+
The read ladder, zero full-log load on the happy path: cached rows → `sessionProjections.restoreFloor` (anchored one event below the lowest usable watermark) → persistence `readFrom(id, floor)` → `sessionProjections.restore` → fail-soft write-back of the refreshed rows. The anchor makes a shrunk log (crash-repair truncation) provable: an overreaching row triggers exactly one full re-read from seq 0 instead of serving a ghost value. No registered units serve `{asOfSeq: -1, values: {}}` without touching persistence; a session with no persisted log rejects with the seam's `not found`.
|
|
35
|
+
|
|
36
|
+
`write(session)` is the synchronous-cut checkpoint both mandatory points use; carriers may call it directly (not fail-soft — the fail-soft wrappers own containment).
|
|
37
|
+
|
|
38
|
+
## Composition
|
|
39
|
+
|
|
40
|
+
```yaml
|
|
41
|
+
- id: session-projection-cache
|
|
42
|
+
name: '@deepseek-ai/dsh-session-projection-cache'
|
|
43
|
+
config:
|
|
44
|
+
writeEveryEvents: 200
|
|
45
|
+
writeIntervalMs: 5000
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Injects `storageDomain`, `sessionProjections`, `sessionPersistence`, `sessions`. Without this row the projection system runs live-only (watermark cache; cold reads fall back to full log loads wherever a carrier implements them).
|
|
49
|
+
|
|
50
|
+
## Model Experience
|
|
51
|
+
|
|
52
|
+
None, as the cache only persists and restores host-side read models of already-logged session state and touches no prompt, message, schema, stream, or tool result.
|
|
53
|
+
|
|
54
|
+
#### KV Cache effect
|
|
55
|
+
|
|
56
|
+
None; the cache never assembles or sends provider requests.
|
|
57
|
+
|
|
58
|
+
## Known Limitations and Deferred Work
|
|
59
|
+
|
|
60
|
+
- **No eviction or retention surface** — records accumulate per session; pruning stored checkpoints is out-of-band maintenance, same stance as session persistence itself.
|
|
61
|
+
- **Interval throttle is per-session coarse** — the timer arms at the first dirty event after a clean write; a steady sub-threshold trickle writes once per interval, not a sliding window.
|
|
62
|
+
- **`coldSnapshot` reads are not deduplicated** — two concurrent cold reads of one session each run the ladder; last write-back wins (rows are equivalent), acceptable for listing-scale call rates.
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# @deepseek-ai/dsh-session-projection-cache
|
|
2
|
+
|
|
3
|
+
[English](README.md) | 中文
|
|
4
|
+
|
|
5
|
+
持久投影缓存(`ctx.sessionProjectionCache`):把每个已注册投影单元的状态持久化为检查点,基于域数据形态(domain data form)每会话一条记录(`session_projcache` 域——出厂 JSON 后端将其落在配置的存储根目录下、`workspace.json` 旁边)。设计权威:[session-projection RFC](../../../.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.md)(persisted projection cache 一节)。
|
|
6
|
+
|
|
7
|
+
一条存储行 `(key → {ver, seq, val})` 是折叠捷径,绝不是权威:可能陈旧(`seq` 精确说明陈旧到哪),但绝不会错。实现据此承诺:
|
|
8
|
+
|
|
9
|
+
- **每次后台写入都 fail-soft。** 持久写失败只记一条警告并保持缓存陈旧;下一次写入或冷读自愈。两次写之间崩溃的代价是更长的尾部回放,绝不是错误的值。
|
|
10
|
+
- **`ver` 与活单元 `stateVersion` 不匹配即丢弃,绝不迁移。** 单元递增版本会在读取时使其行失效;该 key 从日志重新折叠。
|
|
11
|
+
- **整记录写入。** 每次写入替换该会话的完整检查点(注册表切面始终是完整的),并经无损 JSON 边界快照——违反纯 JSON 约定的单元状态会大声失败。
|
|
12
|
+
- **记录绑定到日志生命周期,而不只是 id。** 每条记录存储其折叠来源的 header 身份(`createdAt`、`cwd`);每次读取先以活 header 或存储 header 为证验证它,再接受任何行——被删后重建的 id、或缓存幸存而持久化存储被换掉时,无关记录被整体丢弃,绝不播种幻影值。
|
|
13
|
+
- **日志领先,缓存跟随。** 活会话检查点先把缓冲事件持久 flush,缓存行才落地,因此崩溃只会让缓存落后于日志(更长的尾部回放),绝不领先于它。
|
|
14
|
+
|
|
15
|
+
## 写策略
|
|
16
|
+
|
|
17
|
+
两个必写点,其间节流:
|
|
18
|
+
|
|
19
|
+
| 触发 | 性质 |
|
|
20
|
+
|---|---|
|
|
21
|
+
| `turn/end` | 必写——冷读要的正是轮次终值。 |
|
|
22
|
+
| 会话销毁(detach) | 必写——live 转 cold 的时刻;此后冷读阶梯接管该会话。 |
|
|
23
|
+
| 累计 `writeEveryEvents` 个已提交事件 | 配置节流(条数)。 |
|
|
24
|
+
| 距首个脏事件 `writeIntervalMs` 毫秒 | 配置节流(间隔)。 |
|
|
25
|
+
|
|
26
|
+
两个 `Config` 字段均必填(无默认值):写入节奏是部署选择,没有普适正确值,由 cordis.yml 明示。
|
|
27
|
+
|
|
28
|
+
## 列表读(`cachedSnapshot(meta)`)
|
|
29
|
+
|
|
30
|
+
零 I/O 一档:从身份匹配的存储记录直接 view 全量值(仅版本匹配的 key),以 `{asOfSeq, values}` 切面返回——`asOfSeq` 取所服务行的最低水位,客户端在 higher-seq-wins 规则下播种值存储时,陈旧列表块永远压不过更新的推送帧。无可用记录(未知 id、无关生命周期、无版本匹配行)时返回 `undefined`;api-proxy 列表载体将其转为列缺席。
|
|
31
|
+
|
|
32
|
+
## 冷读(`coldSnapshot(id, signal?)`)
|
|
33
|
+
|
|
34
|
+
读取阶梯,正常路径无需加载全量日志:缓存行 → `sessionProjections.restoreFloor`(锚定在最低可用水位之前一个事件的位置)→ 持久化 `readFrom(id, floor)` → `sessionProjections.restore` → 刷新行的 fail-soft 写回。这个锚使缩短的日志(崩溃修复截断)可被证明:越界的行恰好触发一次从 seq 0 的全量重读,而不是把幽灵值当现值服务。无已注册单元时直接服务 `{asOfSeq: -1, values: {}}`,不触碰持久化;无持久日志的会话以 seam 的 `not found` 拒绝。
|
|
35
|
+
|
|
36
|
+
`write(session)` 是两个必写点共用的同步切面检查点;载体可以直接调用(非 fail-soft——由 fail-soft 包装层负责遏制)。
|
|
37
|
+
|
|
38
|
+
## 组合
|
|
39
|
+
|
|
40
|
+
```yaml
|
|
41
|
+
- id: session-projection-cache
|
|
42
|
+
name: '@deepseek-ai/dsh-session-projection-cache'
|
|
43
|
+
config:
|
|
44
|
+
writeEveryEvents: 200
|
|
45
|
+
writeIntervalMs: 5000
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
注入 `storageDomain`、`sessionProjections`、`sessionPersistence`、`sessions`。没有这一行时,投影系统只跑 live(水位缓存;冷读在实现了它的载体处退回全量日志加载)。
|
|
49
|
+
|
|
50
|
+
## 模型体验
|
|
51
|
+
|
|
52
|
+
无,因为缓存只持久化并恢复 host 侧的、由已写入日志的会话状态派生的读模型,不触碰任何提示词、消息、schema、流或工具结果。
|
|
53
|
+
|
|
54
|
+
#### KV Cache 影响
|
|
55
|
+
|
|
56
|
+
无;缓存从不组装或发送提供方请求。
|
|
57
|
+
|
|
58
|
+
## 已知局限与延后工作
|
|
59
|
+
|
|
60
|
+
- **没有淘汰或保留面**——记录按会话累积;清理存储的检查点是带外维护,与会话持久化本身同一立场。
|
|
61
|
+
- **间隔节流按会话粗粒度**——计时器在一次干净写入后的首个脏事件时武装;持续的低于阈值的涓流每个间隔写一次,不是滑动窗口。
|
|
62
|
+
- **`coldSnapshot` 读取不去重**——同一会话的两个并发冷读各跑一遍阶梯;写回最后者胜(行等价),对列表级调用频率可接受。
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { Service } from "@deepseek-ai/cordis";
|
|
2
|
+
import z from "@deepseek-ai/schemastery";
|
|
3
|
+
import { snapshotJsonValue } from "@deepseek-ai/dsh-session";
|
|
4
|
+
import { z as z$1 } from "zod";
|
|
5
|
+
import { defineDomain, domainTable } from "@deepseek-ai/dsh-storage-domain";
|
|
6
|
+
//#region lib/types/spec.js
|
|
7
|
+
/**
|
|
8
|
+
* The session-projcache domain declaration: one `sessions` table keyed by
|
|
9
|
+
* {@link SessionId}, each record the full projection checkpoint for one
|
|
10
|
+
* session (`key → {ver, seq, val}` rows). The spec object
|
|
11
|
+
* is the single source of the domain's identity, version, and record schema;
|
|
12
|
+
* the storage-domain routing decides the medium (the shipped composition's
|
|
13
|
+
* json backend lands it at `<root>/session_projcache.json`, beside
|
|
14
|
+
* `workspace.json`).
|
|
15
|
+
* @module @deepseek-ai/dsh-session-projection-cache/src/spec
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* One persisted checkpoint row (the RFC's `(sessionId, key, ver, seq, val)`
|
|
19
|
+
* minus the two record keys). `val` is the unit's internal state — plain
|
|
20
|
+
* JSON by the unit contract; `z.json()` enforces that at the durable
|
|
21
|
+
* boundary. A row is never wrong, only possibly stale: `seq` says exactly
|
|
22
|
+
* how stale, and a `ver` mismatch against the live unit's `stateVersion`
|
|
23
|
+
* discards it at read time (never a migration).
|
|
24
|
+
*/
|
|
25
|
+
const checkpointRow = z$1.object({
|
|
26
|
+
ver: z$1.number().int().nonnegative(),
|
|
27
|
+
seq: z$1.number().int().gte(-1),
|
|
28
|
+
val: z$1.json()
|
|
29
|
+
});
|
|
30
|
+
/**
|
|
31
|
+
* The stored-log identity a record is bound to: the immutable header fields
|
|
32
|
+
* that distinguish one session lifecycle from another under the same id. A
|
|
33
|
+
* session id names a slot, not a lifecycle — a deleted-then-recreated id, or
|
|
34
|
+
* a persistence root swapped under a surviving cache, would otherwise let an
|
|
35
|
+
* old row pass every watermark check and seed state folded from an unrelated
|
|
36
|
+
* log. Reads validate this against the live header (listing) or the stored
|
|
37
|
+
* header (cold read) before accepting any row.
|
|
38
|
+
*/
|
|
39
|
+
const checkpointIdentity = z$1.object({
|
|
40
|
+
createdAt: z$1.number().int().nonnegative(),
|
|
41
|
+
cwd: z$1.string().optional()
|
|
42
|
+
});
|
|
43
|
+
/**
|
|
44
|
+
* One session's stored record: the log identity it was folded from plus its
|
|
45
|
+
* checkpoint rows keyed by projection key. The whole record is replaced on
|
|
46
|
+
* every write (whole-value discipline — the registry checkpoint is always
|
|
47
|
+
* the complete per-session cut).
|
|
48
|
+
*/
|
|
49
|
+
const checkpointRecord = z$1.object({
|
|
50
|
+
identity: checkpointIdentity,
|
|
51
|
+
rows: z$1.record(z$1.string(), checkpointRow)
|
|
52
|
+
});
|
|
53
|
+
/**
|
|
54
|
+
* The session-projcache domain spec. Version bumps discard the whole medium
|
|
55
|
+
* (cache semantics: a stale or unreadable cache costs a longer tail replay,
|
|
56
|
+
* never a wrong value).
|
|
57
|
+
*/
|
|
58
|
+
const projectionCacheDomainSpec = defineDomain({
|
|
59
|
+
name: "session_projcache",
|
|
60
|
+
version: 3,
|
|
61
|
+
tables: { sessions: domainTable(checkpointRecord) }
|
|
62
|
+
});
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region lib/types/index.js
|
|
65
|
+
/**
|
|
66
|
+
* Persisted projection cache (`ctx.sessionProjectionCache`): durable
|
|
67
|
+
* checkpoints of every registered projection unit's state, one record per
|
|
68
|
+
* session on the domain data form (`session_projcache` domain — the shipped
|
|
69
|
+
* json backend lands it beside `workspace.json`). The cache is a fold
|
|
70
|
+
* shortcut, never an authority: a row is possibly stale (its `seq`
|
|
71
|
+
* says how stale) but never wrong, so every write path is fail-soft (a lost
|
|
72
|
+
* write costs a longer tail replay on the next cold read) and a
|
|
73
|
+
* `ver` mismatch discards the row instead of migrating it. Design
|
|
74
|
+
* authority: the session-projection RFC
|
|
75
|
+
* (.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.md).
|
|
76
|
+
* @module @deepseek-ai/dsh-session-projection-cache
|
|
77
|
+
*/
|
|
78
|
+
const Config = z.object({
|
|
79
|
+
writeEveryEvents: z.natural().min(1).required(),
|
|
80
|
+
writeIntervalMs: z.natural().min(1).required()
|
|
81
|
+
});
|
|
82
|
+
/**
|
|
83
|
+
* The persisted projection cache service. Opens the `session_projcache`
|
|
84
|
+
* domain at init, checkpoints live sessions on a throttled write-behind
|
|
85
|
+
* (count/interval triggers from {@link Config}) plus two mandatory points —
|
|
86
|
+
* `turn/end` and session disposal (the live-to-cold moment) — and serves the
|
|
87
|
+
* cold-read ladder: cached row, persistence `readFrom` tail, registry
|
|
88
|
+
* `restore`, durable write-back. Every durable write is fail-soft: failures
|
|
89
|
+
* log a warning and the cache self-heals on the next write or cold read.
|
|
90
|
+
*/
|
|
91
|
+
var SessionProjectionCache = class extends Service {
|
|
92
|
+
config;
|
|
93
|
+
static inject = [
|
|
94
|
+
"storageDomain",
|
|
95
|
+
"sessionProjections",
|
|
96
|
+
"sessionPersistence",
|
|
97
|
+
"sessions"
|
|
98
|
+
];
|
|
99
|
+
static Config = Config;
|
|
100
|
+
table;
|
|
101
|
+
dirty = /* @__PURE__ */ new Map();
|
|
102
|
+
constructor(ctx, config) {
|
|
103
|
+
super(ctx, "sessionProjectionCache");
|
|
104
|
+
this.config = config;
|
|
105
|
+
}
|
|
106
|
+
/** Open the domain and install the write-behind listeners. */
|
|
107
|
+
async [Service.init]() {
|
|
108
|
+
const domain = await this.ctx.storageDomain.open(projectionCacheDomainSpec);
|
|
109
|
+
this.ctx.effect(() => () => domain.close(), "sessionProjectionCache.domainClose");
|
|
110
|
+
this.table = domain.table("sessions");
|
|
111
|
+
this.installWritePath();
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* The stored record for one session, accepted only when its bound log
|
|
115
|
+
* identity matches `expected`. A session id names a slot, not a lifecycle:
|
|
116
|
+
* a recreated id or a persistence store swapped under a surviving cache
|
|
117
|
+
* must not let an old record seed state folded from an unrelated log.
|
|
118
|
+
* Synchronous from the domain's in-memory state.
|
|
119
|
+
* @param id - the session whose record is read.
|
|
120
|
+
* @param expected - the log identity the caller holds (live or stored header).
|
|
121
|
+
* @returns the identity-matching record, or `undefined` (absent or unrelated).
|
|
122
|
+
*/
|
|
123
|
+
recordFor(id, expected) {
|
|
124
|
+
const record = this.requireTable().get(id);
|
|
125
|
+
if (record === void 0) return void 0;
|
|
126
|
+
return identityMatches(record.identity, expected) ? record : void 0;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* The zero-I/O listing read: whole values viewed straight from the stored
|
|
130
|
+
* rows (version-matching keys only), each cut carried with its watermark
|
|
131
|
+
* so a client value store can seed under its higher-seq-wins rule — as
|
|
132
|
+
* stale as the last durable checkpoint but never wrong, and never from an
|
|
133
|
+
* unrelated log (the caller's header is the identity witness). Fresher
|
|
134
|
+
* paths (the history tail baseline, {@link coldSnapshot}) supersede these
|
|
135
|
+
* values whenever a session is actually opened.
|
|
136
|
+
* @param meta - the listed session's header (identity witness; no log read).
|
|
137
|
+
* @returns the cut (`asOfSeq` = lowest served-row watermark), or
|
|
138
|
+
* `undefined` when no usable row exists for this lifecycle.
|
|
139
|
+
*/
|
|
140
|
+
cachedSnapshot(meta) {
|
|
141
|
+
const record = this.recordFor(meta.id, identityOf(meta));
|
|
142
|
+
if (record === void 0) return void 0;
|
|
143
|
+
const values = this.ctx.sessionProjections.viewCheckpoint(record.rows);
|
|
144
|
+
const keys = Object.keys(values);
|
|
145
|
+
if (keys.length === 0) return void 0;
|
|
146
|
+
return {
|
|
147
|
+
asOfSeq: Math.min(...keys.map((key) => record.rows[key].seq)),
|
|
148
|
+
values
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Durably checkpoint one live session NOW (both mandatory points call
|
|
153
|
+
* this; tests and carriers may too). The registry cut is snapshotted at
|
|
154
|
+
* this boundary (states are live references), then the whole record is
|
|
155
|
+
* replaced. NOT fail-soft — callers on the fail-soft paths contain it.
|
|
156
|
+
* @param session - the live session to checkpoint.
|
|
157
|
+
* @returns resolution after durability and event emission.
|
|
158
|
+
*/
|
|
159
|
+
async write(session) {
|
|
160
|
+
const rows = this.ctx.sessionProjections.checkpoint(session);
|
|
161
|
+
this.markClean(session);
|
|
162
|
+
if (this.ctx.sessions.get(session.id) === session) await this.ctx.sessions.flush(session);
|
|
163
|
+
await this.put(session.id, identityOf(session.header), rows);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Cold-read one persisted session's projections with zero full-log load:
|
|
167
|
+
* cached rows + a persistence `readFrom` tail from the registry's restore
|
|
168
|
+
* floor, refolded by the registry and written back (fail-soft) so the next
|
|
169
|
+
* cold read starts closer. A cache row invalidated by a shrunk log
|
|
170
|
+
* (crash-repair truncation) triggers one full re-read from seq 0 — the
|
|
171
|
+
* ladder's slow rung, still no crash. Rejects when the session has no
|
|
172
|
+
* persisted log (`not found` from the persistence seam).
|
|
173
|
+
* @param id - the persisted session to read.
|
|
174
|
+
* @param signal - optional cancellation for the persistence reads.
|
|
175
|
+
* @returns the snapshot cut at the stored log end.
|
|
176
|
+
*/
|
|
177
|
+
async coldSnapshot(id, signal) {
|
|
178
|
+
const record = this.requireTable().get(id);
|
|
179
|
+
const cached = record?.rows ?? {};
|
|
180
|
+
const floor = this.ctx.sessionProjections.restoreFloor(cached);
|
|
181
|
+
const persistence = this.ctx.sessionPersistence;
|
|
182
|
+
if (floor === void 0) return {
|
|
183
|
+
asOfSeq: (await persistence.readFrom(id, 0, signal)).events.at(-1)?.seq ?? -1,
|
|
184
|
+
values: {}
|
|
185
|
+
};
|
|
186
|
+
let restored;
|
|
187
|
+
const tail = await persistence.readFrom(id, floor, signal);
|
|
188
|
+
const related = record === void 0 || identityMatches(record.identity, identityOf(tail.meta));
|
|
189
|
+
try {
|
|
190
|
+
if (!related) throw new Error("unrelated log identity");
|
|
191
|
+
restored = this.ctx.sessionProjections.restore(cached, tail.events, floor);
|
|
192
|
+
} catch {
|
|
193
|
+
const whole = await persistence.readFrom(id, 0, signal);
|
|
194
|
+
restored = this.ctx.sessionProjections.restore({}, whole.events, 0);
|
|
195
|
+
}
|
|
196
|
+
await this.putSoft(id, identityOf(tail.meta), restored.checkpoint, "cold-read write-back");
|
|
197
|
+
return restored.snapshot;
|
|
198
|
+
}
|
|
199
|
+
installWritePath() {
|
|
200
|
+
this.ctx.on("session/event", (session, event) => {
|
|
201
|
+
if (event.type === "turn/end") {
|
|
202
|
+
this.flushSoft(session, "turn/end");
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const state = this.dirty.get(session) ?? {
|
|
206
|
+
pending: 0,
|
|
207
|
+
timer: void 0
|
|
208
|
+
};
|
|
209
|
+
this.dirty.set(session, state);
|
|
210
|
+
state.pending += 1;
|
|
211
|
+
if (state.pending >= this.config.writeEveryEvents) {
|
|
212
|
+
this.flushSoft(session, "count threshold");
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
state.timer ??= setTimeout(() => {
|
|
216
|
+
this.flushSoft(session, "interval");
|
|
217
|
+
}, this.config.writeIntervalMs);
|
|
218
|
+
});
|
|
219
|
+
this.ctx.on("session/disposed", (session) => {
|
|
220
|
+
this.flushSoft(session, "detach");
|
|
221
|
+
this.markClean(session);
|
|
222
|
+
this.dirty.delete(session);
|
|
223
|
+
});
|
|
224
|
+
this.ctx.effect(() => () => {
|
|
225
|
+
for (const state of this.dirty.values()) if (state.timer !== void 0) clearTimeout(state.timer);
|
|
226
|
+
this.dirty.clear();
|
|
227
|
+
}, "sessionProjectionCache.timers");
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* One fail-soft durable checkpoint. Every caller has work by construction:
|
|
231
|
+
* the throttle triggers only fire dirty (markClean clears the timer with
|
|
232
|
+
* the counter) and the two mandatory points write unconditionally.
|
|
233
|
+
*/
|
|
234
|
+
async flushSoft(session, trigger) {
|
|
235
|
+
try {
|
|
236
|
+
await this.write(session);
|
|
237
|
+
} catch (error) {
|
|
238
|
+
this.ctx.logger.warn(`session projection cache: ${trigger} write for "${session.id}" failed (cache stays stale): ${String(error)}`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
/** Reset one session's dirty bookkeeping (its checkpoint is being written). */
|
|
242
|
+
markClean(session) {
|
|
243
|
+
const state = this.dirty.get(session);
|
|
244
|
+
if (state === void 0) return;
|
|
245
|
+
state.pending = 0;
|
|
246
|
+
if (state.timer !== void 0) {
|
|
247
|
+
clearTimeout(state.timer);
|
|
248
|
+
state.timer = void 0;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
/** Replace one session's stored record with its log identity and a detached snapshot of `rows`. */
|
|
252
|
+
async put(id, identity, rows) {
|
|
253
|
+
const detached = snapshotJsonValue(rows);
|
|
254
|
+
if (detached === void 0) throw new TypeError("projection checkpoint is not losslessly JSON-serializable (a unit state violates the plain-JSON contract)");
|
|
255
|
+
await this.requireTable().put(id, {
|
|
256
|
+
identity,
|
|
257
|
+
rows: detached
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
/** Fail-soft {@link put}: cache writes must never fail their caller's read or event path. */
|
|
261
|
+
async putSoft(id, identity, rows, what) {
|
|
262
|
+
try {
|
|
263
|
+
await this.put(id, identity, rows);
|
|
264
|
+
} catch (error) {
|
|
265
|
+
this.ctx.logger.warn(`session projection cache: ${what} for "${id}" failed (cache stays stale): ${String(error)}`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
requireTable() {
|
|
269
|
+
/* v8 ignore next -- Service.init assigns the table before the service becomes injectable */
|
|
270
|
+
if (this.table === void 0) throw new Error("session projection cache is not initialized");
|
|
271
|
+
return this.table;
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
/** Project a header onto the identity fields a record is bound to. */
|
|
275
|
+
function identityOf(header) {
|
|
276
|
+
return {
|
|
277
|
+
createdAt: header.createdAt,
|
|
278
|
+
...header.cwd === void 0 ? {} : { cwd: header.cwd }
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
/** Whether a stored record's bound identity names the caller's lifecycle. */
|
|
282
|
+
function identityMatches(stored, expected) {
|
|
283
|
+
return stored.createdAt === expected.createdAt && stored.cwd === expected.cwd;
|
|
284
|
+
}
|
|
285
|
+
//#endregion
|
|
286
|
+
export { Config, SessionProjectionCache, SessionProjectionCache as default, checkpointIdentity, checkpointRecord, checkpointRow, projectionCacheDomainSpec };
|
package/lib/invariant.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
//#region lib/types/invariant.js
|
|
2
|
+
/**
|
|
3
|
+
* Package-owned invariant companion for `@deepseek-ai/dsh-session-projection-cache`.
|
|
4
|
+
* @module @deepseek-ai/dsh-session-projection-cache/invariant
|
|
5
|
+
*/
|
|
6
|
+
const PACKAGE_NAME = "@deepseek-ai/dsh-session-projection-cache";
|
|
7
|
+
/** Cordis companion plugin name. */
|
|
8
|
+
const name = "session-projection-cache-invariant";
|
|
9
|
+
/** Service required before the companion can reserve package ownership. */
|
|
10
|
+
const inject = ["invariants"];
|
|
11
|
+
/**
|
|
12
|
+
* No runtime invariant: the cache's correctness relation (a stored row equals
|
|
13
|
+
* the registry fold at its `seq` watermark) is only checkable by re-running the
|
|
14
|
+
* fold over the persisted log — duplicating the implementation rather than
|
|
15
|
+
* detecting drift — and its staleness is by design (fail-soft writes). The
|
|
16
|
+
* durable boundary is already schema-validated by the storage-domain layer
|
|
17
|
+
* on every reopen, and the read ladder's version/watermark guards are proven
|
|
18
|
+
* by the package spec.
|
|
19
|
+
*/
|
|
20
|
+
const install = () => {};
|
|
21
|
+
/**
|
|
22
|
+
* Register this package's invariant companion.
|
|
23
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
24
|
+
* @returns the installed registration's disposer after setup succeeds.
|
|
25
|
+
*/
|
|
26
|
+
const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
|
|
27
|
+
//#endregion
|
|
28
|
+
export { apply, inject, name };
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persisted projection cache (`ctx.sessionProjectionCache`): durable
|
|
3
|
+
* checkpoints of every registered projection unit's state, one record per
|
|
4
|
+
* session on the domain data form (`session_projcache` domain — the shipped
|
|
5
|
+
* json backend lands it beside `workspace.json`). The cache is a fold
|
|
6
|
+
* shortcut, never an authority: a row is possibly stale (its `seq`
|
|
7
|
+
* says how stale) but never wrong, so every write path is fail-soft (a lost
|
|
8
|
+
* write costs a longer tail replay on the next cold read) and a
|
|
9
|
+
* `ver` mismatch discards the row instead of migrating it. Design
|
|
10
|
+
* authority: the session-projection RFC
|
|
11
|
+
* (.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.md).
|
|
12
|
+
* @module @deepseek-ai/dsh-session-projection-cache
|
|
13
|
+
*/
|
|
14
|
+
import { Context, Service } from '@deepseek-ai/cordis';
|
|
15
|
+
import z from '@deepseek-ai/schemastery';
|
|
16
|
+
import type { Session, SessionHeader, SessionId } from '@deepseek-ai/dsh-session';
|
|
17
|
+
import type { ProjectionSnapshot } from '@deepseek-ai/dsh-session-projection';
|
|
18
|
+
export { checkpointIdentity, checkpointRecord, checkpointRow, projectionCacheDomainSpec } from './spec.ts';
|
|
19
|
+
export type { CheckpointIdentity, CheckpointRecord } from './spec.ts';
|
|
20
|
+
declare module '@deepseek-ai/cordis' {
|
|
21
|
+
interface Context {
|
|
22
|
+
sessionProjectionCache: SessionProjectionCache;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Plugin config. Both throttle triggers are deployment choices with no
|
|
27
|
+
* universally correct value, so the composition states them explicitly
|
|
28
|
+
* (cordis.yml); the two mandatory write points (`turn/end` and session
|
|
29
|
+
* disposal) are policy, not tunables, and always fire.
|
|
30
|
+
*/
|
|
31
|
+
export interface Config {
|
|
32
|
+
/** Committed events per session that force a durable checkpoint write between mandatory points. */
|
|
33
|
+
writeEveryEvents: number;
|
|
34
|
+
/** Longest time (milliseconds) a dirty checkpoint may stay unwritten between mandatory points. */
|
|
35
|
+
writeIntervalMs: number;
|
|
36
|
+
}
|
|
37
|
+
export declare const Config: z<Config>;
|
|
38
|
+
/**
|
|
39
|
+
* The persisted projection cache service. Opens the `session_projcache`
|
|
40
|
+
* domain at init, checkpoints live sessions on a throttled write-behind
|
|
41
|
+
* (count/interval triggers from {@link Config}) plus two mandatory points —
|
|
42
|
+
* `turn/end` and session disposal (the live-to-cold moment) — and serves the
|
|
43
|
+
* cold-read ladder: cached row, persistence `readFrom` tail, registry
|
|
44
|
+
* `restore`, durable write-back. Every durable write is fail-soft: failures
|
|
45
|
+
* log a warning and the cache self-heals on the next write or cold read.
|
|
46
|
+
*/
|
|
47
|
+
export declare class SessionProjectionCache extends Service {
|
|
48
|
+
config: Config;
|
|
49
|
+
static inject: string[];
|
|
50
|
+
static Config: z<Config>;
|
|
51
|
+
private table?;
|
|
52
|
+
private readonly dirty;
|
|
53
|
+
constructor(ctx: Context, config: Config);
|
|
54
|
+
/** Open the domain and install the write-behind listeners. */
|
|
55
|
+
protected [Service.init](): Promise<void>;
|
|
56
|
+
/**
|
|
57
|
+
* The stored record for one session, accepted only when its bound log
|
|
58
|
+
* identity matches `expected`. A session id names a slot, not a lifecycle:
|
|
59
|
+
* a recreated id or a persistence store swapped under a surviving cache
|
|
60
|
+
* must not let an old record seed state folded from an unrelated log.
|
|
61
|
+
* Synchronous from the domain's in-memory state.
|
|
62
|
+
* @param id - the session whose record is read.
|
|
63
|
+
* @param expected - the log identity the caller holds (live or stored header).
|
|
64
|
+
* @returns the identity-matching record, or `undefined` (absent or unrelated).
|
|
65
|
+
*/
|
|
66
|
+
private recordFor;
|
|
67
|
+
/**
|
|
68
|
+
* The zero-I/O listing read: whole values viewed straight from the stored
|
|
69
|
+
* rows (version-matching keys only), each cut carried with its watermark
|
|
70
|
+
* so a client value store can seed under its higher-seq-wins rule — as
|
|
71
|
+
* stale as the last durable checkpoint but never wrong, and never from an
|
|
72
|
+
* unrelated log (the caller's header is the identity witness). Fresher
|
|
73
|
+
* paths (the history tail baseline, {@link coldSnapshot}) supersede these
|
|
74
|
+
* values whenever a session is actually opened.
|
|
75
|
+
* @param meta - the listed session's header (identity witness; no log read).
|
|
76
|
+
* @returns the cut (`asOfSeq` = lowest served-row watermark), or
|
|
77
|
+
* `undefined` when no usable row exists for this lifecycle.
|
|
78
|
+
*/
|
|
79
|
+
cachedSnapshot(meta: SessionHeader): ProjectionSnapshot | undefined;
|
|
80
|
+
/**
|
|
81
|
+
* Durably checkpoint one live session NOW (both mandatory points call
|
|
82
|
+
* this; tests and carriers may too). The registry cut is snapshotted at
|
|
83
|
+
* this boundary (states are live references), then the whole record is
|
|
84
|
+
* replaced. NOT fail-soft — callers on the fail-soft paths contain it.
|
|
85
|
+
* @param session - the live session to checkpoint.
|
|
86
|
+
* @returns resolution after durability and event emission.
|
|
87
|
+
*/
|
|
88
|
+
write(session: Session): Promise<void>;
|
|
89
|
+
/**
|
|
90
|
+
* Cold-read one persisted session's projections with zero full-log load:
|
|
91
|
+
* cached rows + a persistence `readFrom` tail from the registry's restore
|
|
92
|
+
* floor, refolded by the registry and written back (fail-soft) so the next
|
|
93
|
+
* cold read starts closer. A cache row invalidated by a shrunk log
|
|
94
|
+
* (crash-repair truncation) triggers one full re-read from seq 0 — the
|
|
95
|
+
* ladder's slow rung, still no crash. Rejects when the session has no
|
|
96
|
+
* persisted log (`not found` from the persistence seam).
|
|
97
|
+
* @param id - the persisted session to read.
|
|
98
|
+
* @param signal - optional cancellation for the persistence reads.
|
|
99
|
+
* @returns the snapshot cut at the stored log end.
|
|
100
|
+
*/
|
|
101
|
+
coldSnapshot(id: SessionId, signal?: AbortSignal): Promise<ProjectionSnapshot>;
|
|
102
|
+
private installWritePath;
|
|
103
|
+
/**
|
|
104
|
+
* One fail-soft durable checkpoint. Every caller has work by construction:
|
|
105
|
+
* the throttle triggers only fire dirty (markClean clears the timer with
|
|
106
|
+
* the counter) and the two mandatory points write unconditionally.
|
|
107
|
+
*/
|
|
108
|
+
private flushSoft;
|
|
109
|
+
/** Reset one session's dirty bookkeeping (its checkpoint is being written). */
|
|
110
|
+
private markClean;
|
|
111
|
+
/** Replace one session's stored record with its log identity and a detached snapshot of `rows`. */
|
|
112
|
+
private put;
|
|
113
|
+
/** Fail-soft {@link put}: cache writes must never fail their caller's read or event path. */
|
|
114
|
+
private putSoft;
|
|
115
|
+
private requireTable;
|
|
116
|
+
}
|
|
117
|
+
export default SessionProjectionCache;
|
|
118
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package-owned invariant companion for `@deepseek-ai/dsh-session-projection-cache`.
|
|
3
|
+
* @module @deepseek-ai/dsh-session-projection-cache/invariant
|
|
4
|
+
*/
|
|
5
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
6
|
+
/** Cordis companion plugin name. */
|
|
7
|
+
export declare const name = "session-projection-cache-invariant";
|
|
8
|
+
/** Service required before the companion can reserve package ownership. */
|
|
9
|
+
export declare const inject: string[];
|
|
10
|
+
/**
|
|
11
|
+
* Register this package's invariant companion.
|
|
12
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
13
|
+
* @returns the installed registration's disposer after setup succeeds.
|
|
14
|
+
*/
|
|
15
|
+
export declare const apply: (ctx: Context) => Promise<() => void>;
|
|
16
|
+
//# sourceMappingURL=invariant.d.ts.map
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The session-projcache domain declaration: one `sessions` table keyed by
|
|
3
|
+
* {@link SessionId}, each record the full projection checkpoint for one
|
|
4
|
+
* session (`key → {ver, seq, val}` rows). The spec object
|
|
5
|
+
* is the single source of the domain's identity, version, and record schema;
|
|
6
|
+
* the storage-domain routing decides the medium (the shipped composition's
|
|
7
|
+
* json backend lands it at `<root>/session_projcache.json`, beside
|
|
8
|
+
* `workspace.json`).
|
|
9
|
+
* @module @deepseek-ai/dsh-session-projection-cache/src/spec
|
|
10
|
+
*/
|
|
11
|
+
import { z } from 'zod';
|
|
12
|
+
import { SessionId } from '@deepseek-ai/dsh-session';
|
|
13
|
+
/**
|
|
14
|
+
* One persisted checkpoint row (the RFC's `(sessionId, key, ver, seq, val)`
|
|
15
|
+
* minus the two record keys). `val` is the unit's internal state — plain
|
|
16
|
+
* JSON by the unit contract; `z.json()` enforces that at the durable
|
|
17
|
+
* boundary. A row is never wrong, only possibly stale: `seq` says exactly
|
|
18
|
+
* how stale, and a `ver` mismatch against the live unit's `stateVersion`
|
|
19
|
+
* discards it at read time (never a migration).
|
|
20
|
+
*/
|
|
21
|
+
export declare const checkpointRow: z.ZodObject<{
|
|
22
|
+
ver: z.ZodNumber;
|
|
23
|
+
seq: z.ZodNumber;
|
|
24
|
+
val: z.ZodJSONSchema;
|
|
25
|
+
}, z.core.$strip>;
|
|
26
|
+
/**
|
|
27
|
+
* The stored-log identity a record is bound to: the immutable header fields
|
|
28
|
+
* that distinguish one session lifecycle from another under the same id. A
|
|
29
|
+
* session id names a slot, not a lifecycle — a deleted-then-recreated id, or
|
|
30
|
+
* a persistence root swapped under a surviving cache, would otherwise let an
|
|
31
|
+
* old row pass every watermark check and seed state folded from an unrelated
|
|
32
|
+
* log. Reads validate this against the live header (listing) or the stored
|
|
33
|
+
* header (cold read) before accepting any row.
|
|
34
|
+
*/
|
|
35
|
+
export declare const checkpointIdentity: z.ZodObject<{
|
|
36
|
+
createdAt: z.ZodNumber;
|
|
37
|
+
cwd: z.ZodOptional<z.ZodString>;
|
|
38
|
+
}, z.core.$strip>;
|
|
39
|
+
/** The identity fields a record is bound to, inferred from {@link checkpointIdentity}. */
|
|
40
|
+
export type CheckpointIdentity = z.infer<typeof checkpointIdentity>;
|
|
41
|
+
/**
|
|
42
|
+
* One session's stored record: the log identity it was folded from plus its
|
|
43
|
+
* checkpoint rows keyed by projection key. The whole record is replaced on
|
|
44
|
+
* every write (whole-value discipline — the registry checkpoint is always
|
|
45
|
+
* the complete per-session cut).
|
|
46
|
+
*/
|
|
47
|
+
export declare const checkpointRecord: z.ZodObject<{
|
|
48
|
+
identity: z.ZodObject<{
|
|
49
|
+
createdAt: z.ZodNumber;
|
|
50
|
+
cwd: z.ZodOptional<z.ZodString>;
|
|
51
|
+
}, z.core.$strip>;
|
|
52
|
+
rows: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
53
|
+
ver: z.ZodNumber;
|
|
54
|
+
seq: z.ZodNumber;
|
|
55
|
+
val: z.ZodJSONSchema;
|
|
56
|
+
}, z.core.$strip>>;
|
|
57
|
+
}, z.core.$strip>;
|
|
58
|
+
/** One stored per-session checkpoint record, inferred from {@link checkpointRecord}. */
|
|
59
|
+
export type CheckpointRecord = z.infer<typeof checkpointRecord>;
|
|
60
|
+
/**
|
|
61
|
+
* The session-projcache domain spec. Version bumps discard the whole medium
|
|
62
|
+
* (cache semantics: a stale or unreadable cache costs a longer tail replay,
|
|
63
|
+
* never a wrong value).
|
|
64
|
+
*/
|
|
65
|
+
export declare const projectionCacheDomainSpec: {
|
|
66
|
+
name: string;
|
|
67
|
+
version: number;
|
|
68
|
+
tables: {
|
|
69
|
+
sessions: import("@deepseek-ai/dsh-storage-domain").DomainTableSpec<SessionId, {
|
|
70
|
+
identity: {
|
|
71
|
+
createdAt: number;
|
|
72
|
+
cwd?: string | undefined;
|
|
73
|
+
};
|
|
74
|
+
rows: Record<string, {
|
|
75
|
+
ver: number;
|
|
76
|
+
seq: number;
|
|
77
|
+
val: z.core.util.JSONType;
|
|
78
|
+
}>;
|
|
79
|
+
}>;
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
//# sourceMappingURL=spec.d.ts.map
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@deepseek-ai/dsh-session-projection-cache",
|
|
3
|
+
"description": "Persisted projection cache (ctx.sessionProjectionCache): durable per-session projection checkpoints over the domain data form, throttled write-behind, and the cold-read ladder (cache row + persistence tail replay)",
|
|
4
|
+
"version": "0.0.1-rc.1",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "restricted"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
|
|
11
|
+
"directory": "packages/session/session-projection-cache"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "lib/index.js",
|
|
15
|
+
"types": "lib/types/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./lib/types/index.d.ts",
|
|
19
|
+
"default": "./lib/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./invariant": {
|
|
22
|
+
"types": "./lib/types/invariant.d.ts",
|
|
23
|
+
"default": "./lib/invariant.js"
|
|
24
|
+
},
|
|
25
|
+
"./src/*": "./src/*",
|
|
26
|
+
"./package.json": "./package.json"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"lib/index.js",
|
|
30
|
+
"lib/invariant.js",
|
|
31
|
+
"lib/types/**/*.d.ts"
|
|
32
|
+
],
|
|
33
|
+
"license": "BSD-3-Clause",
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"zod": "^4.4.3",
|
|
36
|
+
"@deepseek-ai/schemastery": "^3.18.1-rc.1"
|
|
37
|
+
},
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"@deepseek-ai/dsh-session": "^0.0.1-rc.1",
|
|
40
|
+
"@deepseek-ai/dsh-session-persistence": "^0.0.1-rc.1",
|
|
41
|
+
"@deepseek-ai/dsh-session-projection": "^0.0.1-rc.1",
|
|
42
|
+
"@deepseek-ai/dsh-invariants": "^0.0.1-rc.1",
|
|
43
|
+
"@deepseek-ai/dsh-storage-domain": "^0.0.1-rc.1",
|
|
44
|
+
"@deepseek-ai/cordis": "^4.0.1-rc.1"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@deepseek-ai/dsh-invariants": "^0.0.1-rc.1",
|
|
48
|
+
"@deepseek-ai/dsh-session-projection": "^0.0.1-rc.1",
|
|
49
|
+
"@deepseek-ai/dsh-storage": "^0.0.1-rc.1",
|
|
50
|
+
"@deepseek-ai/dsh-storage-domain": "^0.0.1-rc.1",
|
|
51
|
+
"@deepseek-ai/cordis": "^4.0.1-rc.1",
|
|
52
|
+
"@deepseek-ai/dsh-session": "^0.0.1-rc.1",
|
|
53
|
+
"@deepseek-ai/dsh-session-persistence": "^0.0.1-rc.1"
|
|
54
|
+
}
|
|
55
|
+
}
|