@qilin-agent/session-projection 0.0.0-stage → 3.1.4

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.
@@ -0,0 +1,292 @@
1
+ /**
2
+ * Service Definition and drive registry for the session-projection capability seam: the merge-extensible state and client-view type
3
+ * tables, the `ProjectionDefinition` state-driven computation unit contract,
4
+ * and the `ctx.sessionProjections` registry that DRIVES every registered unit
5
+ * forward eagerly over committed session events. Domain host plugins
6
+ * contribute pure folds and optional client views; the framework owns the
7
+ * subscription, the per-session watermark cache, and change notification;
8
+ * carriers consume the snapshot read face and the change feed. Neither side
9
+ * knows the other
10
+ * (capability-seam three-way split). Design authority: the session-projection
11
+ * RFC (.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.md).
12
+ *
13
+ * Whole-value event rule (load-bearing): a state-carrying log event MUST
14
+ * carry the complete post-change state, never a bare delta — it keeps every
15
+ * unit's transition trivially cheap and every served value self-describing.
16
+ *
17
+ * @module @qilin-agent/session-projection
18
+ */
19
+ import { Context, Service } from '@qilin-agent/kylin';
20
+ import type { ZodType } from 'zod';
21
+ import { SessionLogOffset, SessionSeq } from '@qilin-agent/session';
22
+ import type { Session, SessionEvent, SessionHeader, SessionSeqCursor } from '@qilin-agent/session';
23
+ declare module '@qilin-agent/kylin' {
24
+ interface Context {
25
+ sessionProjections: SessionProjectionRegistry;
26
+ }
27
+ }
28
+ import type { SessionProjectionMap, SessionProjectionStateMap } from './types.ts';
29
+ export type { SessionProjectionMap, SessionProjectionStateMap } from './types.ts';
30
+ /**
31
+ * One domain's state-driven computation unit: a pure synchronous fold plus
32
+ * declarations and an optional client view — never an opaque getter. The framework drives
33
+ * `apply` on every committed session event; the domain holds no
34
+ * subscriptions and owns only the computation. All functions MUST be
35
+ * synchronous (an async unit would tear the carriers' consistency cut), and
36
+ * `state` MUST be plain JSON (the persisted-cache precondition).
37
+ */
38
+ export interface ProjectionDefinition<K extends keyof SessionProjectionStateMap, S extends SessionProjectionStateMap[K] = SessionProjectionStateMap[K]> {
39
+ /** The projection key this unit owns (its `SessionProjectionStateMap` entry). */
40
+ key: K;
41
+ /** Validates persisted state before it seeds a fold. */
42
+ stateSchema: ZodType<S>;
43
+ /**
44
+ * State for the empty log and its immutable Session metadata.
45
+ * @param header - immutable metadata for the Session being projected.
46
+ * @param inheritedEventCount - exact fork-inherited prefix length.
47
+ * @returns the initial state.
48
+ */
49
+ init(header: SessionHeader, inheritedEventCount: SessionLogOffset): NoInfer<S>;
50
+ /**
51
+ * Pure transition: previous state + one committed event → next state. A
52
+ * unit uninterested in an event MUST return the same state reference — an
53
+ * unchanged reference (`Object.is`) produces zero downstream work.
54
+ * @param state - the state covering all prior events.
55
+ * @param event - the next committed session event.
56
+ * @returns the next state (same reference when the event is not the unit's).
57
+ */
58
+ apply(state: NoInfer<S>, event: SessionEvent): NoInfer<S>;
59
+ /** Client view. Omit for host-only units. */
60
+ wire?: K extends keyof SessionProjectionMap ? {
61
+ /** Validates the wire payload before it leaves the host. */
62
+ viewSchema: ZodType<SessionProjectionMap[K]>;
63
+ /**
64
+ * State → wire payload (the read-side projection). The live drive keeps
65
+ * the two latest raw results and compares them with `Object.is`; an
66
+ * object-valued view must reuse its reference to suppress publication
67
+ * across internal-only state changes.
68
+ * @param state - the current state.
69
+ * @returns the whole current value for this unit's key.
70
+ */
71
+ view(state: NoInfer<S>): SessionProjectionMap[K];
72
+ } : never;
73
+ /**
74
+ * Persisted-cache invalidation version: bump whenever the serialized state fields or the
75
+ * fold semantics change, so persisted `(sessionId, key, ver, seq, val)`
76
+ * rows from an older unit are discarded instead of being forward-applied
77
+ * into garbage. Non-negative integer.
78
+ */
79
+ stateVersion: number;
80
+ }
81
+ /**
82
+ * Change-feed listener: one unit's raw `view` result changed by `Object.is`
83
+ * for one session. `value` is the schema-validated output; `seq` is the
84
+ * unit's watermark at emission (the seq of the event that caused the change).
85
+ */
86
+ export type ProjectionChangeListener = (session: Session, key: Extract<keyof SessionProjectionMap, string>, value: unknown, seq: SessionSeq) => void;
87
+ /**
88
+ * One consistent read cut over every registered client-visible unit for one session.
89
+ * `asOfSeq` is the shared watermark — the seq of the last event every value
90
+ * reflects (`-1` for an empty log).
91
+ */
92
+ export interface ProjectionSnapshot {
93
+ /** Seq of the last event the values reflect; -1 for an empty log. */
94
+ asOfSeq: SessionSeqCursor;
95
+ /** Whole current client value per registered key. */
96
+ values: Partial<SessionProjectionMap>;
97
+ }
98
+ /**
99
+ * One unit's checkpoint: its internal state (plain JSON by the unit
100
+ * contract), the seq of the last event folded into it, and the unit
101
+ * `stateVersion` that produced it — the persisted projection-cache row
102
+ * `(sessionId, key, ver, seq, val)` minus the two outer keys. A row is
103
+ * never authoritative, only a fold shortcut: `restore` discards it on a
104
+ * version mismatch or when it claims events past the stored log end.
105
+ */
106
+ export interface ProjectionCheckpointRow {
107
+ /** The registering unit's `stateVersion` at fold time. */
108
+ ver: number;
109
+ /** Seq of the last event folded into `val`; -1 for the empty log. */
110
+ seq: SessionSeqCursor;
111
+ /** The unit's internal state — plain JSON per the unit contract. */
112
+ val: unknown;
113
+ }
114
+ /** Checkpoint rows keyed by projection key (one session's persisted cache value). */
115
+ export type ProjectionCheckpoint = Record<string, ProjectionCheckpointRow>;
116
+ /**
117
+ * `ctx.sessionProjections`: the projection unit table and its drive. The
118
+ * service subscribes to `session/event` once; every committed event passes
119
+ * every registered unit's `apply` (eager drive). A changed state reference
120
+ * computes the next client view; the change feed is notified only when its
121
+ * raw result changes by `Object.is`.
122
+ * Cells build lazily — a unit registered after events flowed, or a session
123
+ * older than the registry, folds `init` over the in-memory log on first
124
+ * touch (event or read). Registration is an effect (disposer rides the
125
+ * calling fiber): an unloaded domain plugin's key disappears from snapshots
126
+ * and clients read it as capability absence. A host reader either declares
127
+ * `sessionProjections` in its plugin `inject` or fails explicitly when the
128
+ * registry or required key is absent. Contributors may preserve optional
129
+ * registration through `ctx.inject(['sessionProjections'], ...)`. Registrants sharing a key
130
+ * share one unit and are counted: the same tool package mounted in N agent
131
+ * presets registers N times, and the key survives until the last one
132
+ * unloads.
133
+ */
134
+ export declare class SessionProjectionRegistry extends Service {
135
+ private readonly registrations;
136
+ private readonly listeners;
137
+ /**
138
+ * Create and install the registry as `ctx.sessionProjections`.
139
+ * @param ctx - Cordis context that owns the service.
140
+ */
141
+ constructor(ctx: Context);
142
+ /**
143
+ * Register one domain's unit. The registration is an effect on the calling
144
+ * context's fiber: disposing the fiber (or calling the returned disposer)
145
+ * removes the key — and the unit's cached cells — from subsequent drives
146
+ * and snapshots.
147
+ * @param definition - key, state schema, pure unit functions, and stateVersion.
148
+ * @returns the exact disposer that unregisters this unit.
149
+ */
150
+ register<K extends keyof SessionProjectionMap, S extends SessionProjectionStateMap[K]>(definition: Omit<ProjectionDefinition<K, S>, 'wire'> & {
151
+ wire: NonNullable<ProjectionDefinition<K, S>['wire']>;
152
+ }): () => void;
153
+ /**
154
+ * Register one host-only unit. Its state is omitted from client snapshots
155
+ * and always checkpointed like every other unit.
156
+ * @param definition - key, state schema, pure unit functions, and stateVersion.
157
+ * @returns the exact disposer that unregisters this unit.
158
+ */
159
+ register<K extends Exclude<keyof SessionProjectionStateMap, keyof SessionProjectionMap>, S extends SessionProjectionStateMap[K]>(definition: Omit<ProjectionDefinition<K, S>, 'wire'>): () => void;
160
+ /**
161
+ * Subscribe to the change feed. The registration is an effect on the
162
+ * calling context's fiber.
163
+ * @param listener - called once per client-visible unit whose raw view changed by `Object.is`, per committed event.
164
+ * @returns the exact disposer that unsubscribes.
165
+ */
166
+ onChanged(listener: ProjectionChangeListener): () => void;
167
+ /**
168
+ * Read one unit's current host state after materializing every registered
169
+ * unit at the Session cursor. Unrelated wire views are not produced.
170
+ * The returned value is live; callers must not mutate it.
171
+ * @param session - the session whose state is read.
172
+ * @param key - the registered unit key.
173
+ * @returns current state, or `undefined` when the key is not registered.
174
+ */
175
+ stateOf<K extends keyof SessionProjectionStateMap>(session: Session, key: K): SessionProjectionStateMap[K] | undefined;
176
+ /**
177
+ * One consistent cut over every registered client-visible unit for one session, read from
178
+ * the watermark cache (missing cells fold lazily over the in-memory log).
179
+ * Fully synchronous — every value and `asOfSeq` reflect the same log
180
+ * position. Each value passes its unit's `viewSchema` before leaving.
181
+ * @param session - the session whose projection values are read.
182
+ * @param keys - optional client-visible outputs; state materialization remains complete.
183
+ * @returns the snapshot; `values` is empty when no selected client-visible unit is registered.
184
+ */
185
+ snapshot(session: Session, keys?: readonly Extract<keyof SessionProjectionMap, string>[]): ProjectionSnapshot;
186
+ /**
187
+ * Read only already-materialized client-visible cells without folding history.
188
+ * Values may trail the live Session and are therefore hints, not a complete
189
+ * baseline. Missing cells are omitted.
190
+ * @param session - attached Session whose cached cells are inspected.
191
+ * @param keys - optional wire keys to view.
192
+ * @returns the lowest common cached cut, or `undefined` when no wire cell exists.
193
+ */
194
+ cachedSnapshot(session: Session, keys?: readonly Extract<keyof SessionProjectionMap, string>[]): ProjectionSnapshot | undefined;
195
+ /**
196
+ * State-level checkpoint of every persisted unit for one session, read
197
+ * from the watermark cache (missing cells fold lazily over the in-memory
198
+ * log). This is the write side of the persisted projection cache: the
199
+ * returned rows are the `(key → {ver, seq, val})` part of the durable
200
+ * `(sessionId, key, ver, seq, val)`
201
+ * rows. Every `val` is a DETACHED structured clone — never the live
202
+ * cell reference: the watermark cache is this registry's authoritative
203
+ * mutable state, and a caller reaching the live reference could corrupt
204
+ * every subsequent snapshot and frame through it (plain JSON by the unit
205
+ * contract, so the clone is total).
206
+ * @param session - the session whose unit states are checkpointed.
207
+ * @returns one row per registered key.
208
+ */
209
+ checkpoint(session: Session): ProjectionCheckpoint;
210
+ /**
211
+ * The stored seq a {@link restore} tail read over `checkpoint` must start
212
+ * at: one event BELOW the lowest usable watermark (a row is usable when
213
+ * its `ver` matches the live unit's `stateVersion`; an absent or mismatched row
214
+ * pulls the floor to `0` — that key must refold the full log). The
215
+ * one-below anchor is load-bearing: the tail then proves how far the
216
+ * stored log still extends, so {@link restore} can detect a log that
217
+ * shrank below a row's watermark (crash-repair truncation) instead of
218
+ * serving the stale row as current — an empty tail read from the anchor
219
+ * yields an end below every watermark and the restore rejects for a full
220
+ * re-read.
221
+ * @param checkpoint - persisted rows for one session (possibly stale or empty).
222
+ * @returns the offset for the stored-log suffix read (`SessionHandle.read`),
223
+ * or `undefined` when no unit is registered (no read needed —
224
+ * {@link restore} would serve empty values regardless).
225
+ */
226
+ restoreFloor(checkpoint: ProjectionCheckpoint): SessionLogOffset | undefined;
227
+ /**
228
+ * View a checkpoint's rows without any log read: for every registered
229
+ * client-visible unit whose row's `ver` matches, serve the schema-validated
230
+ * `view` of the schema-validated stored state; mismatched, malformed, or absent rows leave their key
231
+ * absent (a cold or listing consumer treats it as not-yet-available and a
232
+ * fuller read path refolds it). The zero-I/O rung of the read ladder —
233
+ * values are as stale as their rows, never wrong.
234
+ * @param checkpoint - persisted rows for one session (possibly stale or empty).
235
+ * @param keys - optional wire keys to view.
236
+ * @returns whole values per key with a usable row; empty when none.
237
+ */
238
+ viewCheckpoint(checkpoint: ProjectionCheckpoint, keys?: readonly Extract<keyof SessionProjectionMap, string>[]): Partial<SessionProjectionMap>;
239
+ /**
240
+ * Cold read: fold every persisted unit over a stored log suffix, seeding
241
+ * each from its checkpoint row when usable — the one read recipe (cached
242
+ * state + forward tail replay + `view`) applied without a live `Session`.
243
+ * Call with the stored events at or past `restoreFloor(checkpoint)` (a
244
+ * `SessionHandle.read` slice) and that same floor as
245
+ * `baseSeq`; the floor's one-below anchor makes the supplied end honest,
246
+ * so a shrunk log is detected here. A row is usable iff its
247
+ * `ver` matches the live unit's `stateVersion`, it does not predate `baseSeq`
248
+ * (`seq >= baseSeq - 1`), and it does not claim events past the
249
+ * supplied end (`seq <= endSeq`); an unusable row is discarded
250
+ * and its key refolds from `init` — which is only sound over the full
251
+ * log, so a discarded row with `baseSeq > 0` throws (the caller re-reads
252
+ * from seq 0, e.g. after a crash-repair truncation shrank the log below
253
+ * a row's watermark).
254
+ * @param checkpoint - persisted rows for one session (possibly stale or empty).
255
+ * @param events - the stored events with `seq >= baseSeq`, in seq order.
256
+ * @param baseSeq - the seq `events` starts at (its first event's seq when non-empty).
257
+ * @param header - immutable metadata for the Session being restored.
258
+ * @param inheritedEventCount - exact fork-inherited prefix length supplied to unit initialization.
259
+ * @returns the snapshot cut at the supplied log end (`asOfSeq` is the last
260
+ * supplied event's seq, `baseSeq - 1` for an empty tail) plus the
261
+ * refreshed checkpoint rows at that cut, ready for a durable write-back.
262
+ */
263
+ restore(checkpoint: ProjectionCheckpoint, events: readonly SessionEvent[], baseSeq: SessionLogOffset, header: SessionHeader, inheritedEventCount: SessionLogOffset): {
264
+ snapshot: ProjectionSnapshot;
265
+ checkpoint: ProjectionCheckpoint;
266
+ };
267
+ /**
268
+ * Restore an exact cut and install its states on the supplied prepared Session.
269
+ * A later publication reuses these cells; ordinary live reads and event drive
270
+ * advance any constructor-owned suffix exactly once.
271
+ * @param session - exact prepared Session that owns the restored log prefix.
272
+ * @param checkpoint - persisted rows for this Session lifecycle.
273
+ * @param events - exact events at the observation cut.
274
+ * @param baseSeq - first supplied event sequence.
275
+ * @returns all projection values at the supplied cut.
276
+ */
277
+ hydrate(session: Session, checkpoint: ProjectionCheckpoint, events: readonly SessionEvent[], baseSeq: SessionLogOffset): ProjectionSnapshot;
278
+ /** Materialize every registered unit cell at the Session's current cursor. */
279
+ private materializeCells;
280
+ /** Fold one unit from init over `events`, producing a cell watermarked at the last folded event. */
281
+ private buildCell;
282
+ /** Read (or lazily build, folding the full in-memory log) one unit's cell. */
283
+ private cellFor;
284
+ /** Advance one existing cell through a contiguous Session prefix. */
285
+ private advanceCell;
286
+ /** Eager drive: pass one committed event through every unit; notify on changed raw view references. */
287
+ private drive;
288
+ /** Return one schema-validated wire value. */
289
+ private viewCell;
290
+ }
291
+ export default SessionProjectionRegistry;
292
+ //# sourceMappingURL=index.d.ts.map