@x-otto/persistence 0.0.1-alpha.0

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 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/atomic-write.ts","../src/types.ts","../src/memory-persistence.ts","../src/save-document.ts","../src/disk-persistence.ts","../src/panel-state.ts","../src/paste-state.ts","../src/remote-persistence.ts","../src/file-persistence.ts","../src/errors.ts","../src/storage-factory.ts","../src/change-log.ts","../src/append-log.ts","../src/storage-host.ts","../src/http-append-log.ts","../src/sqlite-db-cache.ts","../src/sqlite-persistence.ts"],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,49 @@
1
+ import{dirname as e,join as t}from"node:path";import{mkdir as n,open as r,readFile as i,readdir as a,rename as o,stat as s,unlink as c}from"node:fs/promises";import{randomUUID as l}from"node:crypto";import{constants as u,mkdirSync as d}from"node:fs";import f from"better-sqlite3";import{StringDecoder as p}from"node:string_decoder";var m=class extends Error{constructor(e,t){super(e,t),this.name=`PersistenceError`}},h=class extends m{statusCode;constructor(e,t,n){super(e,n),this.name=`RemotePersistenceError`,this.statusCode=t}},g=class extends h{info;constructor(e,t,n){super(e,409,n),this.name=`RemoteSnapshotConflictError`,this.info=t}};async function _(t,n){let i=`${t}.${l()}.tmp`;try{let a=await r(i,`w`);try{await a.writeFile(n),await a.sync()}finally{await a.close()}await o(i,t),await ee(e(t))}catch(e){throw await c(i).catch(()=>{}),e instanceof m?e:new m(`Failed to atomically write "${t}"`,{cause:e})}}async function ee(e){let t;try{t=await r(e,`r`),await t.sync()}catch{}finally{t&&await t.close().catch(()=>{})}}var v=class{store=new Map;defaultPageSize;defaultSortBy;constructor(e={}){this.defaultPageSize=e.defaultPageSize??20,this.defaultSortBy=e.defaultSortBy??`updatedAt`}getSortValue(e,t){let n=e.metadata?.[t];return typeof n==`number`?n:0}async save(e){this.store.set(this.extractId(e),structuredClone(e))}async load(e){let t=this.store.get(e);return t?structuredClone(t):null}async delete(e){return this.store.delete(e)}async list(){return Array.from(this.store.keys())}entries(){return Array.from(this.store.entries())}async listPaginated(e){let{page:t,order:n=`desc`}=e,r=e.sortBy??this.defaultSortBy,i=e.pageSize??this.defaultPageSize,a=Array.from(this.store.entries()).map(([e,t])=>({id:e,sortValue:this.getSortValue(t,r)}));a.sort((e,t)=>n===`asc`?e.sortValue-t.sortValue:t.sortValue-e.sortValue);let o=a.length,s=Math.max(1,Math.ceil(o/i)),c=Math.max(0,Math.min(t,s-1)),l=c*i;return{items:a.slice(l,l+i).map(e=>e.id),total:o,page:c,pageSize:i,totalPages:s,hasNext:c<s-1,hasPrevious:c>0}}},y=class extends v{extractId(e){return e.id}};async function b(e,t,n,r){let{now:i,version:a=1,tags:o=[],preserveCreatedAt:s=!0,owner:c,updatedBy:l}=r,u=s?await e.load(t):null,d=u?.metadata?.owner;await e.save({version:a,id:t,data:n,metadata:{createdAt:u?.metadata.createdAt??i,updatedAt:i,tags:o,...d?{owner:d}:c?{owner:c}:{},...l?{updatedBy:l}:{}}})}var x=class{baseDir;extension;defaultPageSize;codec;initialized=!1;constructor(e){this.baseDir=e.baseDir,this.extension=e.extension??`.json`,this.defaultPageSize=e.defaultPageSize??20,this.codec=e.codec??{encode:e=>JSON.stringify(e),decode:e=>JSON.parse(e),contentType:`application/json`}}async save(e){await this.ensureDir(),await _(this.resolvePath(this.extractId(e)),this.codec.encode(e))}async load(e){await this.ensureDir();let t=this.resolvePath(e);try{let e=await i(t,`utf-8`);return this.codec.decode(e)}catch(t){if(S(t))return null;throw new m(`Failed to load "${e}"`,{cause:t})}}async delete(e){try{return await c(this.resolvePath(e)),!0}catch{return!1}}async list(){await this.ensureDir();try{return(await a(this.baseDir)).filter(e=>e.endsWith(this.extension)).map(e=>decodeURIComponent(e.slice(0,-this.extension.length)))}catch{return[]}}async listPaginated(e){await this.ensureDir();let{page:n,order:r=`desc`}=e,i=e.pageSize??this.defaultPageSize;try{let e=(await a(this.baseDir)).filter(e=>e.endsWith(this.extension)),o=await Promise.all(e.map(async e=>{let n=await s(t(this.baseDir,e));return{id:decodeURIComponent(e.slice(0,-this.extension.length)),mtime:n.mtimeMs}}));o.sort((e,t)=>r===`asc`?e.mtime-t.mtime:t.mtime-e.mtime);let c=o.length,l=Math.max(1,Math.ceil(c/i)),u=Math.max(0,Math.min(n,l-1)),d=u*i;return{items:o.slice(d,d+i).map(e=>e.id),total:c,page:u,pageSize:i,totalPages:l,hasNext:u<l-1,hasPrevious:u>0}}catch{return{items:[],total:0,page:0,pageSize:i,totalPages:1,hasNext:!1,hasPrevious:!1}}}resolvePath(e){let n=encodeURIComponent(e);return t(this.baseDir,`${n}${this.extension}`)}async ensureDir(){this.initialized||=(await n(this.baseDir,{recursive:!0}),!0)}};function S(e){return typeof e==`object`&&!!e&&`code`in e&&e.code===`ENOENT`}var C=class{map=new Map;constructor(e){this.extractId=e}async save(e){this.map.set(this.extractId(e),e)}async load(e){return this.map.get(e)??null}async delete(e){return this.map.delete(e)}},w=class extends x{extractId(e){return e.sessionId}},T=class{store;constructor(e){this.store=new w({baseDir:e.baseDir,extension:`.json`})}async save(e){return this.store.save(e)}async load(e){return this.store.load(e)}async delete(e){return this.store.delete(e)}};function E(){return new C(e=>e.sessionId)}var te=class extends x{extractId(e){return e.sessionId}},D=class{store;constructor(e){this.store=new te({baseDir:e.baseDir,extension:`.json`})}async save(e){return this.store.save(e)}async load(e){return this.store.load(e)}async delete(e){return this.store.delete(e)}};function O(e){let t=typeof e==`object`&&e?e:{},n=typeof t.nextId==`number`&&Number.isFinite(t.nextId)?Math.floor(t.nextId):0,r=Math.max(1,n),i=Array.isArray(t.entries)?t.entries:[];for(let e of i){let t=e?.id;typeof t==`number`&&Number.isFinite(t)&&(r=Math.max(r,Math.floor(t)+1))}return r}function k(e,t){let n=typeof t==`object`&&t?t:{},r=Array.isArray(n.entries)?n.entries:[],i=[],a=new Set;for(let e of r){if(typeof e!=`object`||!e)continue;let{id:t,text:n}=e;if(typeof t!=`number`||!Number.isFinite(t)||typeof n!=`string`)continue;let r=Math.floor(t);a.has(r)||(a.add(r),i.push({id:r,text:n,byteLength:Buffer.byteLength(n,`utf-8`)}))}return{sessionId:e,entries:i,nextId:O(n)}}function A(){return new C(e=>e.sessionId)}var j=class{baseUrl;resolveWsKey;get wsKey(){return this.resolveWsKey()}getAuth;getHeaders;fetch;timeoutMs;defaultPageSize;defaultSortBy;codec;get serviceLabel(){return`远端持久化服务`}get endpointFlag(){return`--persistence-url`}constructor(e){this.baseUrl=e.baseUrl.replace(/\/+$/,``);let t=e.wsKey;this.resolveWsKey=typeof t==`function`?()=>t()||`default`:()=>t||`default`,this.getAuth=e.getAuth,this.getHeaders=e.getHeaders??(async()=>({})),this.fetch=e.fetch,this.timeoutMs=e.timeoutMs,this.defaultPageSize=e.defaultPageSize??20,this.defaultSortBy=e.defaultSortBy??`updatedAt`,this.codec=e.codec??{encode:e=>JSON.stringify(e),decode:e=>JSON.parse(e),contentType:`application/json`}}wsPath(e){return`/${encodeURIComponent(this.wsKey)}/${encodeURIComponent(e)}`}wsListPath(){return`/${encodeURIComponent(this.wsKey)}`}async save(e){await this.request(this.wsPath(this.extractId(e)),{method:`PUT`,body:this.codec.encode(e)})}async saveWithRevision(e,t){let n={};t!==void 0&&(n[`If-Match`]=String(t));let r=(await this.request(this.wsPath(this.extractId(e)),{method:`PUT`,body:this.codec.encode(e)},n)).headers.get(`ETag`);return r?Number(r.replace(/"/g,``)):NaN}async loadWithRevision(e){let t=await this.request(this.wsPath(e),{method:`GET`});if(t.status===404)return null;let n=await t.text(),r;try{r=this.codec.decode(n)}catch(n){throw new h(`Remote persistence decode failed: ${this.wsPath(e)}`,t.status,{cause:n})}let i=t.headers.get(`ETag`),a=i?Number(i.replace(/"/g,``)):0;return{snapshot:r,revision:a}}async load(e){return(await this.loadWithRevision(e))?.snapshot??null}async delete(e){return(await this.request(this.wsPath(e),{method:`DELETE`})).ok}async list(){return(await(await this.request(this.wsListPath(),{method:`GET`})).json()).ids}async listPaginated(e){let{page:t,order:n=`desc`}=e,r=e.sortBy??this.defaultSortBy,i=e.pageSize??this.defaultPageSize,a=new URLSearchParams({page:String(t),pageSize:String(i),sortBy:r,order:n});return(await this.request(`${this.wsListPath()}?${a.toString()}`,{method:`GET`})).json()}async request(e,t,n){let r=await this.getAuth(),i={...await this.getHeaders(),...r.token?{Authorization:`Bearer ${r.token}`}:{},Accept:`application/json`,...n};t.body&&(i[`Content-Type`]=this.codec.contentType??`application/json`);let a=new AbortController,o=setTimeout(()=>a.abort(),this.timeoutMs),s=`${this.baseUrl}${e}`;try{let n=await this.fetch(s,{method:t.method,headers:i,body:t.body,signal:a.signal});if(n.status===409){let t=null;try{let e=await n.clone().json();t=typeof e.revision==`number`?e.revision:null}catch{}throw new g(`Remote persistence CAS conflict: ${this.wsPath(e.split(`/`).pop()??``)}`,{revision:t})}if(!n.ok&&n.status!==404)throw new h(`Remote persistence API error: ${n.status} ${n.statusText}`,n.status);return n}catch(e){if(e instanceof g||e instanceof h)throw e;if(e instanceof DOMException&&e.name===`AbortError`)throw new h(`无法连接到${this.serviceLabel}(请求超时 ${this.timeoutMs/1e3}s):${t.method} ${s}\n提示:请确认${this.serviceLabel}是否已启动、${this.endpointFlag} 地址与端口是否正确。`,0,{cause:e});let n=e?.cause?.code,r=n?n===`ECONNREFUSED`?`(连接被拒绝)`:n===`ENOTFOUND`?`(DNS 解析失败,地址不存在)`:n===`ECONNRESET`?`(连接被重置)`:n===`ETIMEDOUT`?`(连接超时)`:n===`CERT_HAS_EXPIRED`?`(TLS 证书已过期)`:n===`DEPTH_ZERO_SELF_SIGNED_CERT`?`(自签名证书被拒)`:`(${n})`:``,i=e instanceof Error?e.message:String(e);throw new h(`无法连接到${this.serviceLabel}:${t.method} ${s}\n原因:${i}${r}\n提示:请确认${this.serviceLabel}是否已启动、${this.endpointFlag} 地址与端口是否正确。`,0,{cause:e})}finally{clearTimeout(o)}}},M=class extends x{constructor(e){super({baseDir:e.baseDir,extension:e.extension,codec:e.codec})}extractId(e){return e.id}};const N=new Map,P=[{version:1,up:e=>{e.exec(`
2
+ -- snapshots (SqlitePersistence)
3
+ CREATE TABLE IF NOT EXISTS snapshots (
4
+ ns TEXT NOT NULL,
5
+ id TEXT NOT NULL,
6
+ version INTEGER NOT NULL DEFAULT 1,
7
+ data TEXT NOT NULL,
8
+ created_at INTEGER NOT NULL,
9
+ updated_at INTEGER NOT NULL,
10
+ tags TEXT NOT NULL DEFAULT '[]',
11
+ PRIMARY KEY (ns, id)
12
+ );
13
+ CREATE INDEX IF NOT EXISTS idx_snapshots_ns_updated
14
+ ON snapshots(ns, updated_at DESC);
15
+ CREATE INDEX IF NOT EXISTS idx_snapshots_ns_created
16
+ ON snapshots(ns, created_at DESC);
17
+
18
+ -- change_log (SqlitePersistence)
19
+ CREATE TABLE IF NOT EXISTS change_log (
20
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
21
+ ns TEXT NOT NULL,
22
+ doc_id TEXT NOT NULL,
23
+ op TEXT NOT NULL,
24
+ ts INTEGER NOT NULL
25
+ );
26
+ CREATE INDEX IF NOT EXISTS idx_change_log_ns_ts
27
+ ON change_log(ns, ts);
28
+ `)}},{version:2,up:e=>{e.exec(`
29
+ -- append_log(RFC-089 M28 事件溯源日志表结构;消费实现 SqliteAppendLog 已删除——
30
+ -- 终局架构 review 2026-07-10 P1-7 确认零生产接线,迁移本身保留不动以维持已发布
31
+ -- migration 历史的不可变性,仅此注释更新去掉对已删除类名的引用)
32
+ CREATE TABLE IF NOT EXISTS append_log (
33
+ stream_id TEXT NOT NULL,
34
+ seq INTEGER NOT NULL,
35
+ payload TEXT NOT NULL,
36
+ PRIMARY KEY (stream_id, seq)
37
+ );
38
+ `)}}],F=new Set;function I(e){return e.reduce((e,t)=>Math.max(e,t.version),0)}function L(e,t,n){let r=I(n);if(t===`:memory:`){if(e.pragma(`user_version`,{simple:!0})===0){for(let t of n)t.up(e);e.pragma(`user_version = ${r}`)}return}if(F.has(t))return;let i=e.pragma(`user_version`,{simple:!0});if(i===0)for(let t of n)t.up(e);else if(i<r)for(let t of n)t.version>i&&t.up(e);else if(i>r)throw Error(`DB at ${t} has schema v${i} > code v${r}. Downgrade not supported. Upgrade otto first.`);e.pragma(`user_version = ${r}`),F.add(t)}function R(t,n=!0,r=P){if(t===`:memory:`){let e=new f(`:memory:`);return L(e,t,r),e}let i=N.get(t);if(i){let e=I(r);if(i.migrationTarget!==e)throw Error(`acquireDb cache conflict at ${t}: cached schema v${i.migrationTarget} but requested migrationSet targets v${e}. 同一 db 路径不可注入不同 migration 段。`);return i.refs++,i.db}d(e(t),{recursive:!0});let a=new f(t);return n&&(a.pragma(`journal_mode = WAL`),a.pragma(`synchronous = NORMAL`),a.pragma(`busy_timeout = 5000`)),L(a,t,r),N.set(t,{db:a,refs:1,migrationTarget:I(r)}),a}function z(e){if(e.name===`:memory:`){e.close();return}let t=e.name,n=N.get(t);n&&(n.refs--,n.refs<=0&&(e.close(),N.delete(t)))}var B=class{db;ns;defaultPageSize;codec;constructor(e){this.ns=e.namespace,this.defaultPageSize=e.defaultPageSize??20,this.codec=e.codec??{encode:e=>JSON.stringify(e),decode:e=>JSON.parse(e),contentType:`application/json`},this.db=R(e.dbPath,e.wal!==!1,P)}close(){z(this.db)}async save(e){let t=e.id,n=e.version??1,r=this.codec.encode(e),i=e.metadata?.updatedAt??Date.now(),a=e.metadata?.createdAt??i,o=JSON.stringify(e.metadata?.tags??[]),s=this.db.prepare(`
39
+ INSERT INTO snapshots (ns, id, version, data, created_at, updated_at, tags)
40
+ VALUES (?, ?, ?, ?, ?, ?, ?)
41
+ ON CONFLICT (ns, id) DO UPDATE SET
42
+ version = excluded.version,
43
+ data = excluded.data,
44
+ updated_at = excluded.updated_at,
45
+ tags = excluded.tags
46
+ `),c=this.db.prepare(`INSERT INTO change_log (ns, doc_id, op, ts) VALUES (?, ?, 'save', ?)`);this.db.transaction(()=>{s.run(this.ns,t,n,r,a,i,o),c.run(this.ns,t,i)})()}async load(e){let t=this.db.prepare(`SELECT data FROM snapshots WHERE ns = ? AND id = ?`).get(this.ns,e);return t?this.codec.decode(t.data):null}async delete(e){let t=this.db.prepare(`DELETE FROM snapshots WHERE ns = ? AND id = ?`),n=this.db.prepare(`INSERT INTO change_log (ns, doc_id, op, ts) VALUES (?, ?, 'delete', ?)`);return this.db.transaction(()=>t.run(this.ns,e).changes>0?(n.run(this.ns,e,Date.now()),!0):!1)()}async list(){return this.db.prepare(`SELECT id FROM snapshots WHERE ns = ? ORDER BY updated_at DESC`).all(this.ns).map(e=>e.id)}async listPaginated(e){let{page:t,order:n=`desc`}=e,r=e.sortBy??`updated_at`,i=e.pageSize??this.defaultPageSize,a=r===`created_at`?`created_at`:`updated_at`,o=n===`asc`?`ASC`:`DESC`,{count:s}=this.db.prepare(`SELECT COUNT(*) as count FROM snapshots WHERE ns = ?`).get(this.ns),c=s,l=Math.max(1,Math.ceil(c/i)),u=Math.max(0,Math.min(t,l-1)),d=u*i;return{items:this.db.prepare(`SELECT id FROM snapshots WHERE ns = ? ORDER BY ${a} ${o} LIMIT ? OFFSET ?`).all(this.ns,i,d).map(e=>e.id),total:c,page:u,pageSize:i,totalPages:l,hasNext:u<l-1,hasPrevious:u>0}}getChangeLog(e,t,n=100){return this.db.prepare(`SELECT id, ns, doc_id, op, ts FROM change_log WHERE ns = ? AND ts >= ? ORDER BY ts ASC, id ASC LIMIT ?`).all(e,t,n)}getChangesSince(e,t,n){let r=`SELECT id, updated_at FROM snapshots WHERE ns = ? AND updated_at > ?`,i=[e,t];n&&n.length>0&&(r+=` AND id IN (${n.map(()=>`?`).join(`,`)})`,i.push(...n)),r+=` ORDER BY updated_at ASC`;let a=this.db.prepare(r).all(...i),o=a.length>0?a.reduce((e,t)=>Math.max(e,t.updated_at),0):this.getLatestCursor(e);return{ids:a.map(e=>e.id),cursor:o}}getDeletedSince(e,t){return this.db.prepare(`SELECT doc_id FROM change_log WHERE ns = ? AND op = 'delete' AND ts > ?`).all(e,t).map(e=>e.doc_id)}getLatestCursor(e){return this.db.prepare(`SELECT MAX(updated_at) as ts FROM snapshots WHERE ns = ?`).get(e)?.ts??0}getLatestChangeTs(e){return this.db.prepare(`SELECT MAX(ts) as ts FROM change_log WHERE ns = ?`).get(e)?.ts??0}getInfo(){let e=this.db.pragma(`journal_mode`),[{count:t}]=this.db.prepare(`SELECT COUNT(*) as count FROM snapshots`).all(),[{count:n}]=this.db.prepare(`SELECT COUNT(*) as count FROM change_log`).all(),[{count:r}]=this.db.prepare(`SELECT COUNT(*) as count FROM snapshots WHERE ns = 'sessions'`).all(),i=this.db.pragma(`integrity_check`),a=i.length===1&&i[0]?.integrity_check===`ok`;return{walEnabled:e===`wal`,snapshots:t,changeLogEntries:n,sessions:r,integrity:a?`ok`:`fail: ${JSON.stringify(i)}`}}};function ne(e){switch(e.type){case`memory`:return new y;case`file`:return new M({baseDir:e.baseDir,extension:e.extension,codec:e.codec});case`sqlite`:return new B({dbPath:e.dbPath,wal:e.wal,namespace:e.namespace,defaultPageSize:e.defaultPageSize,codec:e.codec});default:throw Error(`Unsupported storage type: ${e.type}`)}}const re=u.O_DSYNC==null?u.O_WRONLY|u.O_CREAT|u.O_APPEND:u.O_WRONLY|u.O_CREAT|u.O_APPEND|u.O_DSYNC,V=1024*1024,H=1e3,U=1e4;function W(e,t){e.anchors.push(t),e.anchors.length>U&&e.anchors.splice(0,Math.ceil(U*.1))}function G(e,t){let n=t?.from??0,r=t?.to??1/0;return e>=n&&e<r}var K=class{streams=new Map;listeners=new Map;maxRecordsPerStream;onEvict;constructor(e){this.maxRecordsPerStream=e?.maxRecordsPerStream??1e4,this.onEvict=e?.onEvict}getOrCreateStream(e){let t=this.streams.get(e);return t||(t={records:[],nextSeq:0,droppedCount:0},this.streams.set(e,t)),t}async append(e,t){let n=this.getOrCreateStream(e),r={seq:n.nextSeq,streamId:e,entry:structuredClone(t)};if(n.nextSeq++,n.records.push(r),n.records.length>this.maxRecordsPerStream){let t=n.records.shift();n.droppedCount++,this.onEvict?.(e,t.seq,n.droppedCount)}let i=this.listeners.get(e);if(i)for(let e of i)e(structuredClone(r));return structuredClone(r)}async*read(e,t){let n=this.streams.get(e)?.records??[];for(let e of n)G(e.seq,t)&&(yield structuredClone(e))}async count(e){return this.streams.get(e)?.nextSeq??0}async*tail(e){let t=[],n=null,r=e=>{t.push(e),n&&=(n(),null)},i=this.listeners.get(e)??new Set;i.add(r),this.listeners.set(e,i);let a=-1;try{for(let t of this.streams.get(e)?.records??[])yield structuredClone(t),a=t.seq;for(;;){t.length===0&&await new Promise(e=>{n=e});for(let e of t.splice(0,t.length))e.seq>a&&(yield e,a=e.seq)}}finally{i.delete(r)}}async truncate(e,t){let n=this.streams.get(e);if(!n)return;if(t<=0){this.streams.delete(e);return}let r=n.records.findIndex(e=>e.seq>=t);r!==-1&&(n.records.length=r,n.nextSeq=t)}async clear(e){this.streams.delete(e)}},q=class{baseDir;extension;codec;pollIntervalMs;seqCounters=new Map;byteIndexes=new Map;handles=new Map;repairedStreams=new Set;writeQueue=Promise.resolve();dirReady=!1;closed=!1;constructor(e){this.baseDir=e.baseDir,this.extension=e.extension??`.jsonl`,this.pollIntervalMs=e.pollIntervalMs??50,this.codec=e.codec??{encode:e=>JSON.stringify(e),decode:e=>JSON.parse(e)}}append(e,t){if(this.closed)return Promise.resolve({seq:-1,streamId:e,entry:t});let n=this.writeQueue.then(()=>this.appendSerial(e,t));return this.writeQueue=n.catch(()=>void 0),n}async appendSerial(e,t){await this.ensureDir(),this.repairedStreams.has(e)||(await this.repairPartialTail(e),this.repairedStreams.add(e));let n=await this.nextSeq(e),r={seq:n,streamId:e,entry:t},i=`${this.codec.encode(t)}\n`,a=Buffer.byteLength(i,`utf-8`),o=this.handles.get(e);try{o||=await this.openResidentHandle(e);try{await o.handle.writeFile(i)}catch{await this.invalidateHandle(e),o=await this.openResidentHandle(e),await o.handle.writeFile(i)}}catch(t){throw await this.invalidateHandle(e).catch(()=>void 0),new m(`Failed to append to stream "${e}"`,{cause:t})}let s=this.byteIndexes.get(e);return s&&s.tailSeq===n&&this.recordIndexPoint(e,n,o.tailByteOffset,a),o.tailByteOffset+=a,o.lastUsedAt=Date.now(),this.seqCounters.set(e,n+1),r}async openResidentHandle(e){let t=Date.now();for(let[n,r]of this.handles)n!==e&&t-r.lastUsedAt>6e4&&(this.handles.delete(n),await r.handle.close().catch(()=>void 0));for(;this.handles.size>=8;){let e,t=1/0;for(let[n,r]of this.handles)r.lastUsedAt<t&&(t=r.lastUsedAt,e=n);if(e===void 0)break;let n=this.handles.get(e);this.handles.delete(e),await n.handle.close().catch(()=>void 0)}let n=await r(this.resolvePath(e),re),{size:i}=await n.stat(),a={handle:n,lastUsedAt:t,tailByteOffset:i};return this.handles.set(e,a),a}async invalidateHandle(e){let t=this.handles.get(e);t&&(this.handles.delete(e),await t.handle.close().catch(()=>void 0))}recordIndexPoint(e,t,n,r){let i=this.byteIndexes.get(e);!i||t<i.tailSeq||(t%H===0&&W(i,{seq:t,byteOffset:n}),i.tailSeq=t+1,i.tailByteOffset=n+r)}ensureByteIndex(e){let t=this.byteIndexes.get(e);return t||(t={anchors:[],tailSeq:0,tailByteOffset:0},this.byteIndexes.set(e,t)),t}nearestAnchor(e,t){let n={seq:0,byteOffset:0};for(let r of e.anchors)if(r.seq<=t&&r.seq>=n.seq)n=r;else if(r.seq>t)break;return n}async repairPartialTail(e){await this.invalidateHandle(e);let t=this.resolvePath(e),n;try{n=await r(t,`r+`)}catch(t){if(Z(t))return;throw new m(`Failed to read stream "${e}"`,{cause:t})}try{let{size:e}=await n.stat();if(e===0)return;let t=Buffer.alloc(1);if(await n.read(t,0,1,e-1),t[0]===10)return;let r=-1,i=e;for(;i>0&&r===-1;){let e=Math.max(0,i-65536),t=Buffer.alloc(i-e);await n.read(t,0,t.length,e);for(let n=t.length-1;n>=0;n--)if(t[n]===10){r=e+n+1;break}i=e}await n.truncate(r===-1?0:r)}catch(t){throw new m(`Failed to repair stream "${e}"`,{cause:t})}finally{await n.close()}}async*read(e,t){let n=t?.from??0,i=t?.to??1/0;if(i<=n)return;let a;try{a=await r(this.resolvePath(e),`r`)}catch(t){if(Z(t))return;throw new m(`Failed to read stream "${e}"`,{cause:t})}try{let t=this.ensureByteIndex(e),r=n<t.tailSeq?this.nearestAnchor(t,n):{seq:t.tailSeq,byteOffset:t.tailByteOffset},o=Buffer.alloc(V),s=new p(`utf-8`),c=``,l=r.seq,u=r.byteOffset,d=r.byteOffset;for(;;){let r=d,{bytesRead:f}=await a.read(o,0,V,d);if(f===0)break;d+=f;let p=0;for(;l<n&&l<t.tailSeq&&p<f;){let e=o.subarray(p,f).indexOf(10);if(e===-1){p=f;break}p+=e+1,l++,u=r+p}if(p>=f)continue;c+=s.write(o.subarray(p,f));let m;for(;(m=c.indexOf(`
47
+ `))>=0;){let r=c.slice(0,m),a=u+Buffer.byteLength(r,`utf-8`)+1;if(c=c.slice(m+1),r.length===0){u=a;continue}if(l>=t.tailSeq&&(l%H===0&&W(t,{seq:l,byteOffset:u}),t.tailSeq=l+1,t.tailByteOffset=a),l>=n&&(yield{seq:l,streamId:e,entry:this.codec.decode(r)}),l++,u=a,l>=i)return}}}finally{await a.close()}}async count(e){let t=this.seqCounters.get(e);if(t!==void 0)return t;let n=await this.countLines(e);return this.seqCounters.set(e,n),n}async*tail(e){let t=this.resolvePath(e),n=0,i=0,a=``;for(;!this.closed;){let o;try{o=await r(t,`r`)}catch(t){if(Z(t)){await X(this.pollIntervalMs);continue}throw new m(`Failed to read stream "${e}"`,{cause:t})}try{let{size:t}=await o.stat();if(t<i&&(i=0,n=0,a=``),t>i){let r=t-i,s=Buffer.alloc(r);await o.read(s,0,r,i),i=t,a+=s.toString(`utf-8`);let c;for(;(c=a.indexOf(`
48
+ `))>=0;){let t=a.slice(0,c);a=a.slice(c+1),t.length!==0&&(yield{seq:n,streamId:e,entry:this.codec.decode(t)},n++)}}}finally{await o.close()}if(this.closed)break;await X(this.pollIntervalMs)}}async close(){this.closed=!0,await this.writeQueue.catch(()=>void 0);for(let[,e]of this.handles)await e.handle.close().catch(()=>void 0);this.handles.clear()}truncate(e,t){if(t<=0)return this.clear(e);let n=this.writeQueue.then(()=>this.truncateSerial(e,t));return this.writeQueue=n.catch(()=>void 0),n}async truncateSerial(e,t){await this.invalidateHandle(e);let n;try{n=await r(this.resolvePath(e),`r+`)}catch(t){if(Z(t))return;throw new m(`Failed to read stream "${e}"`,{cause:t})}try{let e=Buffer.alloc(V),r=0,i=0,a=-1;outer:for(;;){let{bytesRead:o}=await n.read(e,0,V,null);if(o===0)break;for(let n=0;n<o;n++)if(e[n]===10&&(r++,r===t)){a=i+n+1;break outer}i+=o}if(a===-1)return;await n.truncate(a)}catch(t){throw new m(`Failed to truncate stream "${e}"`,{cause:t})}finally{await n.close()}this.seqCounters.set(e,t),this.byteIndexes.delete(e)}clear(e){let t=this.writeQueue.then(()=>this.clearSerial(e));return this.writeQueue=t.catch(()=>void 0),t}async clearSerial(e){await this.invalidateHandle(e),this.seqCounters.delete(e),this.byteIndexes.delete(e);try{await c(this.resolvePath(e))}catch(t){if(!Z(t))throw new m(`Failed to clear stream "${e}"`,{cause:t})}}async countLines(e){let t;try{t=await r(this.resolvePath(e),`r`)}catch(t){if(Z(t))return 0;throw new m(`Failed to read stream "${e}"`,{cause:t})}try{let e=Buffer.alloc(V),n=0;for(;;){let{bytesRead:r}=await t.read(e,0,V,null);if(r===0)break;for(let t=0;t<r;t++)e[t]===10&&n++}return n}finally{await t.close()}}async nextSeq(e){let t=this.seqCounters.get(e);if(t!==void 0)return t;let n=await this.countLines(e);return this.seqCounters.set(e,n),n}resolvePath(e){return t(this.baseDir,`${encodeURIComponent(e)}${this.extension}`)}async ensureDir(){this.dirReady||=(await n(this.baseDir,{recursive:!0}),!0)}};function J(e){switch(e.type){case`memory`:return new K;case`file`:return new q({baseDir:e.baseDir,extension:e.extension,codec:e.codec,pollIntervalMs:e.pollIntervalMs});default:throw Error(`Unsupported append-log type: ${e.type}`)}}var Y=class{subs=new Map;publish(e){let t=this.subs.get(e.streamId);if(t)for(let n of[...t])n(e)}subscribe(e,t){let n=this.subs.get(e);return n||this.subs.set(e,n=new Set),n.add(t),()=>{let n=this.subs.get(e);n&&(n.delete(t),n.size===0&&this.subs.delete(e))}}};function ie(e){let t=new Y;return{store:{async append(n,r){let i=await e.append(n,r);return t.publish(i),i},read:(t,n)=>e.read(t,n),tail:t=>e.tail(t),truncate:(t,n)=>e.truncate(t,n),clear:t=>e.clear(t),...e.count?{count:t=>e.count(t)}:{},...e.close?{close:()=>e.close()}:{}},live:t}}function X(e){return new Promise(t=>{setTimeout(t,e).unref?.()})}function Z(e){return typeof e==`object`&&!!e&&`code`in e&&e.code===`ENOENT`}function Q(e){let t=e;return typeof t.getChangeLog==`function`&&typeof t.getChangesSince==`function`&&typeof t.getDeletedSince==`function`}const ae=[`sessions`];var $=class{namespaces;backend;now;instances=new Map;appendLogs=new Map;constructor(e){this.namespaces=new Set(e.namespaces),this.backend=e.backend,this.now=e.now??(()=>Date.now())}has(e){return this.namespaces.has(e)}listNamespaces(){return[...this.namespaces]}wsId(e,t){return`${e?.trim()||`default`}/${t}`}async appendEntry(e,t,n,r){if(!this.namespaces.has(e))throw Error(`Unknown storage namespace: ${e}`);let i=this.appendLogs.get(e);return i||(i=new K,this.appendLogs.set(e,i)),i.append(this.wsId(t,n),r)}async save(e,t,n,r,i){await b(this.resolve(e),this.wsId(t,n),r,{now:this.now(),owner:i,updatedBy:i})}async updateMetadata(e,t,n,r,i){let a=this.resolve(e),o=await a.load(this.wsId(t,n));if(!o)throw Error(`Document "${n}" not found in "${e}"`);o.metadata={...o.metadata,...r},i&&(o.metadata.updatedBy=i),await a.save(o)}async load(e,t,n){let r=await this.resolve(e).load(this.wsId(t,n));return r?r.data:null}async delete(e,t,n){return this.resolve(e).delete(this.wsId(t,n))}async list(e,t,n,r){let i=await this.resolve(e).list(),a=`${t||`default`}/`,o=[];for(let e of i)e.startsWith(a)&&o.push({clean:e.slice(a.length),prefixed:e});let s=o.map(e=>e.clean);if(!n||!r||r===`all`)return s;let c=[];for(let{clean:t,prefixed:i}of o){let a=await this.resolve(e).load(i);if(!a)continue;let o=a.metadata;(r===`owned`&&o.owner===n||r===`visible`&&(o.owner===n||o.visibility===`shared`||!o.visibility))&&c.push(t)}return c}async getChangesSince(e,t,n,r){let i=this.resolve(e),a=t?`${t}/`:`default/`;if(Q(i)){let t=r?.map(e=>`${a}${e}`),o=i.getChangesSince(e,n,t),s=new Map;for(let e of o.ids){if(!e.startsWith(a))continue;let t=e.slice(a.length),n=await i.load(e);s.set(t,n?.data??null)}let c=i.getDeletedSince(e,n);for(let e of c){if(!e.startsWith(a))continue;let t=e.slice(a.length);s.has(t)||s.set(t,null)}return{documents:s,cursor:o.cursor}}let o=await i.list(),s=new Map,c=n;for(let e of o){if(!e.startsWith(a))continue;let t=e.slice(a.length);if(r&&!r.includes(t))continue;let o=await i.load(e);o&&o.metadata.updatedAt>n&&(s.set(t,o.data),c=Math.max(c,o.metadata.updatedAt))}return{documents:s,cursor:c}}async listPaginated(e,t,n){let r=this.resolve(e),i=await r.list(),a=`${t||`default`}/`,o=new Map;for(let e of i)e.startsWith(a)&&o.set(e,e.slice(a.length));let s=[];for(let[e,t]of o){let n=await r.load(e);n&&s.push({...n,id:t})}let c=n.order===`asc`?1:-1;s.sort((e,t)=>c*(this.sortValue(e,n.sortBy)-this.sortValue(t,n.sortBy)));let l=s.length,u=Math.max(1,Math.ceil(l/n.pageSize)),d=Math.max(0,Math.min(n.page,u-1)),f=d*n.pageSize;return{items:s.slice(f,f+n.pageSize).map(e=>e.id),total:l,page:d,pageSize:n.pageSize,totalPages:u,hasNext:d<u-1,hasPrevious:d>0}}sortValue(e,t){let n=e.metadata[t];if(typeof n==`number`)return n;let r=e.data?.metadata?.[t];return typeof r==`number`?r:e.metadata.updatedAt}async*streamEvents(e,t,n,r=500,i){if(!this.namespaces.has(e))return;let a=this.resolve(e);if(!Q(a))return;let o=n,s=-1;for(;!i?.aborted&&(await se(r,i),!i?.aborted);){let n=a.getChangeLog(e,o),r=`${t||`default`}/`;for(let e of n){if(i?.aborted)break;e.id<=s||e.doc_id.startsWith(r)&&(yield{...e,doc_id:e.doc_id.slice(r.length)},o=Math.max(o,e.ts),s=Math.max(s,e.id))}}}resolve(e){if(!this.namespaces.has(e))throw Error(`Unknown storage namespace: ${e}`);let t=this.instances.get(e);return t||(t=this.backend(e),this.instances.set(e,t)),t}};function oe(e){return new $({namespaces:e.namespaces??ae,backend:n=>new M({baseDir:t(e.baseDir,n)}),now:e.now})}function se(e,t){return new Promise(n=>{if(t?.aborted){n();return}let r=setTimeout(n,e);t&&t.addEventListener(`abort`,()=>{clearTimeout(r),n()},{once:!0})})}var ce=class{baseUrl;getAuth;getHeaders;fetchImpl;timeoutMs;pollIntervalMs;flushAtSize;codec;buffer=new Map;flushingStreams=new Set;flushTimer;closed=!1;constructor(e){this.baseUrl=e.baseUrl.replace(/\/+$/,``),this.getAuth=e.getAuth,this.getHeaders=e.getHeaders??(async()=>({})),this.fetchImpl=e.fetch??globalThis.fetch,this.timeoutMs=e.timeoutMs??3e4,this.pollIntervalMs=e.pollIntervalMs??500,this.flushAtSize=e.flushAtSize??100,this.codec=e.codec??{encode:e=>JSON.stringify(e),decode:e=>JSON.parse(e)};let t=e.flushIntervalMs??2e3;this.flushTimer=setInterval(()=>{this.flushAll().catch(()=>{})},t),this.flushTimer.unref?.()}async append(e,t){let n=this.codec.encode(t);return new Promise((t,r)=>{if(this.closed){r(Error(`HttpAppendLog closed — buffered events lost`));return}let i=this.buffer.get(e)??[];i.push({encoded:n,resolve:t,reject:r}),this.buffer.set(e,i),i.length>=this.flushAtSize&&this.tryFlush(e).catch(()=>{})})}async tryFlush(e){if(this.flushingStreams.has(e))return;let t=this.buffer.get(e);if(!(!t||t.length===0)){this.flushingStreams.add(e);try{await this.flushOne(e,t)}finally{this.flushingStreams.delete(e)}}}async flushOne(e,t){this.buffer.delete(e);try{let{seqs:n}=await this.request(`/trace/${encodeURIComponent(e)}`,`POST`,t.map(e=>e.encoded));for(let r=0;r<t.length;r++){let i=n[r]??-1,a=this.codec.decode(t[r].encoded);t[r].resolve({seq:i,streamId:e,entry:a})}}catch{if(this.closed)for(let e of t)e.reject(Error(`HttpAppendLog closed — buffered events lost`));else{let n=this.buffer.get(e)??[];this.buffer.set(e,[...n,...t])}}}async flushAll(){let e=[...this.buffer.keys()];for(let t of e){if(this.flushingStreams.has(t))continue;let e=this.buffer.get(t);if(e&&e.length>0){this.flushingStreams.add(t);try{await this.flushOne(t,e)}finally{this.flushingStreams.delete(t)}}}}async*read(e,t){let n=new URLSearchParams;t?.from!==void 0&&n.set(`from`,String(t.from)),t?.to!==void 0&&n.set(`to`,String(t.to));let r=n.toString(),i=`/trace/${encodeURIComponent(e)}${r?`?${r}`:``}`,{events:a}=await this.request(i,`GET`);for(let n=0;n<a.length;n++){let r=this.codec.decode(a[n]);yield{seq:(t?.from??0)+n,streamId:e,entry:r}}}async*tail(e){let t=-1;{let n=`/trace/${encodeURIComponent(e)}`,{events:r}=await this.request(n,`GET`);for(let n=0;n<r.length;n++){let i=this.codec.decode(r[n]),a=n;t=a,yield{seq:a,streamId:e,entry:i}}}for(;!this.closed;){let n=t+1,r=`/trace/${encodeURIComponent(e)}?from=${n}`;try{let{events:i}=await this.request(r,`GET`);for(let r=0;r<i.length;r++){let a=this.codec.decode(i[r]),o=n+r;t=o,yield{seq:o,streamId:e,entry:a}}}catch{}await le(this.pollIntervalMs)}}async count(e){let{count:t}=await this.request(`/trace/${encodeURIComponent(e)}/count`,`GET`);return t+(this.buffer.get(e)?.length??0)}async truncate(e,t){await this.request(`/trace/${encodeURIComponent(e)}/truncate`,`POST`,{toSeq:t})}async clear(e){await this.request(`/trace/${encodeURIComponent(e)}`,`DELETE`)}async close(){this.closed=!0,this.flushTimer&&=(clearInterval(this.flushTimer),void 0),await this.flushAll()}async request(e,t,n){let r=await this.getAuth(),i={...await this.getHeaders(),...r.token?{Authorization:`Bearer ${r.token}`}:{},Accept:`application/json`};n!==void 0&&(i[`Content-Type`]=`application/json`);let a=new AbortController,o=setTimeout(()=>a.abort(),this.timeoutMs),s=`${this.baseUrl}${e}`;try{let e=await this.fetchImpl(s,{method:t,headers:i,body:n===void 0?void 0:JSON.stringify(n),signal:a.signal});if(!e.ok)throw Error(`HTTP ${e.status}: ${e.statusText}`);return await e.json()}finally{clearTimeout(o)}}};function le(e){return new Promise(t=>{setTimeout(t,e).unref?.()})}export{T as DiskPanelStatePersistence,D as DiskPasteStatePersistence,x as DiskPersistence,q as FileAppendLog,M as FilePersistence,ce as HttpAppendLog,K as MemoryAppendLog,y as MemoryPersistence,v as MemoryPersistenceBase,m as PersistenceError,j as RemotePersistence,h as RemotePersistenceError,g as RemoteSnapshotConflictError,B as SqlitePersistence,$ as StorageHost,R as acquireDb,_ as atomicWriteFile,J as createAppendLog,oe as createFileStorageHost,E as createInMemoryPanelStatePersistence,A as createInMemoryPasteStatePersistence,ie as createLiveTappedAppendLog,ne as createPersistence,k as normalizePasteSnapshot,z as releaseDb,O as resolvePasteNextId,b as saveDocument};
49
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["DEFAULT_PAGE_SIZE","isEnoent","delay","delay"],"sources":["../src/errors.ts","../src/atomic-write.ts","../src/memory-persistence.ts","../src/save-document.ts","../src/disk-persistence.ts","../src/in-memory-entity-persistence.ts","../src/panel-state.ts","../src/paste-state.ts","../src/remote-persistence.ts","../src/file-persistence.ts","../src/sqlite-db-cache.ts","../src/sqlite-persistence.ts","../src/storage-factory.ts","../src/append-log.ts","../src/storage-host.ts","../src/http-append-log.ts"],"sourcesContent":["export class PersistenceError extends Error {\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options)\n this.name = 'PersistenceError'\n }\n}\n\nexport class RemotePersistenceError extends PersistenceError {\n public readonly statusCode: number\n\n constructor(message: string, statusCode: number, options?: { cause?: unknown }) {\n super(message, options)\n this.name = 'RemotePersistenceError'\n this.statusCode = statusCode\n }\n}\n\n/** 409 conflict response 的可选 payload(If-Match CAS 冲突时,authority 返回当前 revision)。 */\nexport interface SnapshotConflictInfo {\n /** 服务端当前 revision;可能为 null(极少数并发删除后不存在)。 */\n revision: number | null\n}\n\n/** 远端快照写入冲突(CAS 失败)——base class 上特化,调用方可显式识别并做 reload/rebase。 */\nexport class RemoteSnapshotConflictError extends RemotePersistenceError {\n public readonly info: SnapshotConflictInfo\n\n constructor(message: string, info: SnapshotConflictInfo, options?: { cause?: unknown }) {\n super(message, 409, options)\n this.name = 'RemoteSnapshotConflictError'\n this.info = info\n }\n}\n","import { dirname } from 'node:path'\nimport { open, rename, unlink } from 'node:fs/promises'\nimport { randomUUID } from 'node:crypto'\nimport { PersistenceError } from './errors'\n\n/**\n * 崩溃安全的全量文件写入(RFC-088 D4)。\n *\n * 语义:write-temp → fsync(fd) → close → rename → fsync(dir, best-effort)。\n * rename 在同一文件系统内原子——读者要么看到旧内容、要么看到新内容,绝不看到半写。\n * fsync(fd) 保证 temp 内容在 rename 前已落盘(否则崩溃/断电后 rename 生效但数据丢失);\n * fsync(dir) 保证 rename 这一目录项变更本身落盘(best-effort——部分平台目录 fsync 不支持,忽略其错误)。\n *\n * 用于**全量覆写**路径(snapshot 保存、append-log 的 repair/truncate 重写),非追加热路径\n * (追加路径用 O_DSYNC 打开,见 FileAppendLog)。\n */\nexport async function atomicWriteFile(filePath: string, data: string | Uint8Array): Promise<void> {\n const tmpPath = `${filePath}.${randomUUID()}.tmp`\n try {\n const handle = await open(tmpPath, 'w')\n try {\n await handle.writeFile(data)\n await handle.sync()\n } finally {\n await handle.close()\n }\n await rename(tmpPath, filePath)\n await syncDir(dirname(filePath))\n } catch (error) {\n // 清理残留 temp(rename 成功后 unlink 会 ENOENT,忽略)。\n await unlink(tmpPath).catch(() => {})\n throw error instanceof PersistenceError\n ? error\n : new PersistenceError(`Failed to atomically write \"${filePath}\"`, { cause: error })\n }\n}\n\n/**\n * fsync 目录项(best-effort)。POSIX 上 rename 的持久化需 fsync 父目录;\n * 部分平台(含部分 Windows/网络文件系统)不支持目录 fsync,此时静默忽略——\n * 数据 fsync(fd)已保证内容落盘,目录 fsync 缺失最坏是崩溃后 rename 回滚,不会半写。\n */\nasync function syncDir(dir: string): Promise<void> {\n let handle: Awaited<ReturnType<typeof open>> | undefined\n try {\n handle = await open(dir, 'r')\n await handle.sync()\n } catch {\n // 目录 fsync 不支持/失败 → best-effort 忽略。\n } finally {\n // handle 为 undefined(open 失败)时不能 `handle?.close().catch()`——可选链在 close() 处短路后\n // `.catch` 作用于 undefined 会抛 TypeError,污染 finally 并向上传播成假失败。显式 if 守卫。\n if (handle) {\n await handle.close().catch(() => {})\n }\n }\n}\n","import type { PaginatedResult, Persistence, Snapshot } from './types'\n\nconst DEFAULT_PAGE_SIZE = 20\n\nexport interface MemoryPersistenceBaseOptions {\n defaultPageSize?: number\n defaultSortBy?: string\n}\n\n/**\n * 抽象内存后端,与 `DiskPersistence<S>` / `RemotePersistence<S>` 对称(都是 `extractId` +\n * 通用 `listPaginated`)。领域包用\"薄子类 + extractId/getSortValue 配置\"复用,无需各写一套\n * Map+分页。原设计稿 docs/design/storage-kernel.md 已随 RFC-067 M123-03 文档收敛删除,\n * 内容并入 packages/persistence/ARCHITECTURE.md,见 commit 5d6a9f5b。\n */\nexport abstract class MemoryPersistenceBase<S> {\n private readonly store = new Map<string, S>()\n protected readonly defaultPageSize: number\n protected readonly defaultSortBy: string\n\n constructor(options: MemoryPersistenceBaseOptions = {}) {\n this.defaultPageSize = options.defaultPageSize ?? DEFAULT_PAGE_SIZE\n this.defaultSortBy = options.defaultSortBy ?? 'updatedAt'\n }\n\n protected abstract extractId(record: S): string\n\n /** 默认读 `record.metadata[sortBy]`;时间字段命名不同的领域可覆盖。 */\n protected getSortValue(record: S, sortBy: string): number {\n const metadata = (record as { metadata?: Record<string, unknown> }).metadata\n const value = metadata?.[sortBy]\n return typeof value === 'number' ? value : 0\n }\n\n async save(record: S): Promise<void> {\n this.store.set(this.extractId(record), structuredClone(record))\n }\n\n async load(id: string): Promise<S | null> {\n const record = this.store.get(id)\n return record ? structuredClone(record) : null\n }\n\n async delete(id: string): Promise<boolean> {\n return this.store.delete(id)\n }\n\n async list(): Promise<string[]> {\n return Array.from(this.store.keys())\n }\n\n /**\n * 受保护的只读快照迭代器——供领域子类(如 session 域的 `InMemorySessionPersistence`)\n *实现自己的排序/过滤查询(如 RFC-154 D1 的 `listRecentMeta`),不在通用层硬编码领域\n * 专属语义(如 archived 过滤)。返回浅拷贝数组,调用方不应修改 store 内容。\n */\n protected entries(): ReadonlyArray<readonly [string, S]> {\n return Array.from(this.store.entries())\n }\n\n async listPaginated(options: {\n page: number\n pageSize?: number\n sortBy?: string\n order?: 'asc' | 'desc'\n }): Promise<PaginatedResult<string>> {\n const { page, order = 'desc' } = options\n const sortBy = options.sortBy ?? this.defaultSortBy\n const pageSize = options.pageSize ?? this.defaultPageSize\n\n const entries = Array.from(this.store.entries()).map(([id, record]) => ({\n id,\n sortValue: this.getSortValue(record, sortBy),\n }))\n\n entries.sort((a, b) => (order === 'asc' ? a.sortValue - b.sortValue : b.sortValue - a.sortValue))\n\n const total = entries.length\n const totalPages = Math.max(1, Math.ceil(total / pageSize))\n const safePage = Math.max(0, Math.min(page, totalPages - 1))\n const start = safePage * pageSize\n const items = entries.slice(start, start + pageSize).map((e) => e.id)\n\n return {\n items,\n total,\n page: safePage,\n pageSize,\n totalPages,\n hasNext: safePage < totalPages - 1,\n hasPrevious: safePage > 0,\n }\n }\n}\n\nexport class MemoryPersistence<T = unknown>\n extends MemoryPersistenceBase<Snapshot<T>>\n implements Persistence<T>\n{\n protected extractId(snapshot: Snapshot<T>): string {\n return snapshot.id\n }\n}\n","import type { Persistence } from './types'\n\n/**\n * 信封构造单一来源(engine-nodes 范式外推)。\n *\n * 内核拥有信封构造:消费者只交 document + 少量选项,内核统一产出\n * `{version, id, data, metadata{createdAt, updatedAt, tags}}` 信封 + 时间戳策略。\n *\n * upsert 默认保留原 createdAt(先 load 再写);append-only 场景传 `preserveCreatedAt: false` 跳过读。\n * 时间戳由调用方注入(`now`),内核不直调 Date.now(确定性/可注入时钟)。\n */\nexport async function saveDocument<T>(\n persistence: Persistence<T>,\n id: string,\n data: T,\n options: {\n now: number\n version?: number\n tags?: string[]\n preserveCreatedAt?: boolean\n owner?: string\n updatedBy?: string\n },\n): Promise<void> {\n const { now, version = 1, tags = [], preserveCreatedAt = true, owner, updatedBy } = options\n const existing = preserveCreatedAt ? await persistence.load(id) : null\n\n const existingOwner = existing?.metadata?.owner as string | undefined\n\n await persistence.save({\n version,\n id,\n data,\n metadata: {\n createdAt: (existing?.metadata.createdAt ?? now) as number,\n updatedAt: now,\n tags,\n ...(existingOwner ? { owner: existingOwner } : owner ? { owner } : {}),\n ...(updatedBy ? { updatedBy } : {}),\n },\n })\n}\n\n","import { join } from 'node:path'\nimport { readFile, readdir, unlink, mkdir, stat } from 'node:fs/promises'\nimport { PersistenceError } from './errors'\nimport { atomicWriteFile } from './atomic-write'\nimport type { PaginatedResult, Codec } from './types'\n\nexport interface DiskPersistenceOptions<S = unknown> {\n baseDir: string\n extension?: string\n defaultPageSize?: number\n codec?: Codec<S>\n}\n\nexport abstract class DiskPersistence<S> {\n protected readonly baseDir: string\n protected readonly extension: string\n protected readonly defaultPageSize: number\n protected readonly codec: Codec<S>\n private initialized = false\n\n constructor(options: DiskPersistenceOptions<S>) {\n this.baseDir = options.baseDir\n this.extension = options.extension ?? '.json'\n this.defaultPageSize = options.defaultPageSize ?? 20\n this.codec = options.codec ?? {\n encode: (snapshot: S) => JSON.stringify(snapshot),\n decode: (payload: string) => JSON.parse(payload) as S,\n contentType: 'application/json',\n }\n }\n\n protected abstract extractId(snapshot: S): string\n\n async save(snapshot: S): Promise<void> {\n await this.ensureDir()\n\n const filePath = this.resolvePath(this.extractId(snapshot))\n const data = this.codec.encode(snapshot)\n\n // RFC-088 D4:崩溃安全全量写(temp → fsync → rename → fsync(dir))。\n // 原实现 temp+rename 无 fsync——崩溃/断电时 rename 可生效但 temp 内容未落盘 → 恢复到空/半写快照。\n await atomicWriteFile(filePath, data)\n }\n\n async load(id: string): Promise<S | null> {\n await this.ensureDir()\n const filePath = this.resolvePath(id)\n\n try {\n const data = await readFile(filePath, 'utf-8')\n return this.codec.decode(data)\n } catch (error) {\n if (isEnoent(error)) {\n return null\n }\n throw new PersistenceError(`Failed to load \"${id}\"`, { cause: error })\n }\n }\n\n async delete(id: string): Promise<boolean> {\n try {\n await unlink(this.resolvePath(id))\n return true\n } catch {\n return false\n }\n }\n\n async list(): Promise<string[]> {\n await this.ensureDir()\n\n try {\n const files = await readdir(this.baseDir)\n return files\n .filter((f) => f.endsWith(this.extension))\n .map((f) => decodeURIComponent(f.slice(0, -this.extension.length)))\n } catch {\n return []\n }\n }\n\n async listPaginated(options: {\n page: number\n pageSize?: number\n sortBy?: string\n order?: 'asc' | 'desc'\n }): Promise<PaginatedResult<string>> {\n await this.ensureDir()\n const { page, order = 'desc' } = options\n const pageSize = options.pageSize ?? this.defaultPageSize\n\n try {\n const files = await readdir(this.baseDir)\n const matched = files.filter((f) => f.endsWith(this.extension))\n\n const withStats = await Promise.all(\n matched.map(async (f) => {\n const filePath = join(this.baseDir, f)\n const s = await stat(filePath)\n return { id: decodeURIComponent(f.slice(0, -this.extension.length)), mtime: s.mtimeMs }\n }),\n )\n\n withStats.sort((a, b) => (order === 'asc' ? a.mtime - b.mtime : b.mtime - a.mtime))\n\n const total = withStats.length\n const totalPages = Math.max(1, Math.ceil(total / pageSize))\n const safePage = Math.max(0, Math.min(page, totalPages - 1))\n const start = safePage * pageSize\n const items = withStats.slice(start, start + pageSize).map((f) => f.id)\n\n return {\n items,\n total,\n page: safePage,\n pageSize,\n totalPages,\n hasNext: safePage < totalPages - 1,\n hasPrevious: safePage > 0,\n }\n } catch {\n return {\n items: [],\n total: 0,\n page: 0,\n pageSize,\n totalPages: 1,\n hasNext: false,\n hasPrevious: false,\n }\n }\n }\n\n private resolvePath(id: string): string {\n const safeId = encodeURIComponent(id)\n return join(this.baseDir, `${safeId}${this.extension}`)\n }\n\n private async ensureDir(): Promise<void> {\n if (this.initialized) {\n return\n }\n\n await mkdir(this.baseDir, { recursive: true })\n this.initialized = true\n }\n}\n\nfunction isEnoent(error: unknown): boolean {\n return (\n typeof error === 'object' &&\n error !== null &&\n 'code' in error &&\n (error as { code: string }).code === 'ENOENT'\n )\n}\n","import type { EntityPersistenceShape } from './types'\n\n/**\n * RFC-345:EntityPersistenceShape 的通用内存实现(影子模式用)。\n *\n * 用于 PanelState / PasteState 等「按单一 id 一对一存取、从不翻页」的实体持久化契约的\n * 内存后端——影子会话零落盘,面板态/粘贴态只活在进程内存,退出即随之释放。\n *\n * 由 §D1 白名单装配点(session-pool-factory / create-agent-runtime)在影子态注入,\n * 替换 DiskPanelStatePersistence / DiskPasteStatePersistence。\n *\n * @param extractId 从快照取主键(PanelState/PasteState 均为 sessionId)。\n */\nexport class InMemoryEntityPersistence<S> implements EntityPersistenceShape<S> {\n private readonly map = new Map<string, S>()\n\n constructor(private readonly extractId: (snapshot: S) => string) {}\n\n async save(snapshot: S): Promise<void> {\n this.map.set(this.extractId(snapshot), snapshot)\n }\n\n async load(id: string): Promise<S | null> {\n return this.map.get(id) ?? null\n }\n\n async delete(id: string): Promise<boolean> {\n return this.map.delete(id)\n }\n}\n","import { DiskPersistence } from './disk-persistence'\nimport { InMemoryEntityPersistence } from './in-memory-entity-persistence'\nimport type { EntityPersistenceShape } from './types'\n\n// ---------------------------------------------------------------------------\n// RFC-108 D2:面板态持久化契约。这些类型是 @x-otto/session 包 `PersistedSessionConfig`\n// 中同名字段的内联副本——persistence 包不应反向依赖 session 包,且这些类型均为纯\n// primitive 接口,无复杂依赖,内联复制是当前最干净的处理方式(未来若类型演化成跨包\n// 共享的复杂结构,可考虑抽共享 types 包)。\n// ---------------------------------------------------------------------------\n\n/** 持久化任务面板条目(内联自 @x-otto/session PersistedSessionConfig.todoList)。 */\nexport interface PanelStateTodoItem {\n id?: string\n title?: string\n status?: 'pending' | 'in_progress' | 'done' | 'skipped' | 'failed'\n turn?: number\n}\n\n/** 持久化编辑文件条目(内联自 @x-otto/session PersistedSessionConfig.editedFiles)。 */\nexport interface PanelStateEditedFile {\n path: string\n operation: 'edit' | 'write' | 'delete'\n addedLines: number\n removedLines: number\n timestamp: number\n turnId?: string\n toolCallId?: string\n}\n\n/** 持久化子代理条目(内联自 @x-otto/session SubagentEntry)。 */\nexport interface PanelStateSubagentEntry {\n id: string\n name: string\n status: 'running' | 'done' | 'error' | 'killed'\n startedAt: number\n turnId?: string\n}\n\n/** 持久化草稿条目(内联自 @x-otto/session DraftEntry)。 */\nexport interface PanelStateDraftEntry {\n id: string\n name: string\n text: string\n savedAt: number\n}\n\n/**\n * 持久化回合完成摘要条目(内联自 @x-otto/session TurnSummaryEntry)。\n *\n * 用户反馈:TUI 底部\"✓ 完成 · N 个工具 · 耗时 · tokens · 模型\"这行摘要此前只是\n * `reduceToTui` 在 `prompt.end` 时从瞬时状态(ReducerState 计数器 + 当次事件字段)现算\n * 现造的 `ChatMessage`(`role:'summary'`),从未写入 `session.messages()`(发给模型的\n * 真实历史)也从未写入任何持久化存储——resume 时 `agentMessagesToChatMessages` 只回放\n * user/assistant/tool_result,摘要行永久丢失。\n *\n * 本字段独立于 `todoList`/`editedFiles`——它是**每回合累加**(不像 editedFiles 那样\n * `prompt.start` 清空只留当前回合),因为 resume 后需要在对话流里逐回合重现历史摘要行,\n * 不是只留最后一条。与 messages()/AgentMessage[] 物理隔离存储,天然不会被\n * provider 序列化层读取——满足\"持久化但不发给大模型\"的约束。\n */\nexport interface PanelStateTurnSummary {\n turn: number\n status: 'done' | 'error' | 'interrupted' | 'incomplete'\n toolCount: number\n elapsedMs: number\n tokensUsed?: number\n cacheHitRatio?: number\n model?: string\n tasksDone?: number\n tasksTotal?: number\n completedAt: number\n}\n\n/**\n * RFC-108 D2:PanelState 持久化信封。\n *\n * `sessionId` 既是业务关联键也是持久化主键——PanelState 始终按 sessionId 一对一存取,\n * 从不翻页/列表查询,故契约用 `EntityPersistenceShape` 而非 `PersistenceShape`。\n */\nexport interface PanelStateSnapshot {\n sessionId: string\n todoList?: PanelStateTodoItem[]\n editedFiles?: PanelStateEditedFile[]\n subagents?: PanelStateSubagentEntry[]\n drafts?: PanelStateDraftEntry[]\n turnSummaries?: PanelStateTurnSummary[]\n}\n\n/** RFC-108 D2:PanelState 持久化契约(无分页三方法)。 */\nexport interface PanelStatePersistence extends EntityPersistenceShape<PanelStateSnapshot> {}\n\n// ---------------------------------------------------------------------------\n// 具体实现:本地磁盘后端\n// ---------------------------------------------------------------------------\n\n/**\n * 内部委托类——继承 `DiskPersistence<PanelStateSnapshot>` 仅用于复用其实\n * save/load/delete 的磁盘 I/O 实现(原子写、版本门、codec 等),不暴露 list/listPaginated。\n */\nclass PanelStateDiskStore extends DiskPersistence<PanelStateSnapshot> {\n protected extractId(snapshot: PanelStateSnapshot): string {\n return snapshot.sessionId\n }\n}\n\n/**\n * RFC-108 D2:PanelState 本地磁盘持久化实现。\n *\n * 对外仅暴露 `PanelStatePersistence` 三方法(save/load/delete),内部委托\n * `DiskPersistence<PanelStateSnapshot>` 负责实际的磁盘 I/O。\n *\n * 参照 `RemoteSessionPersistence extends RemotePersistence<SessionSnapshot<T>>`\n * 的“领域固化 PersistenceShape”模式——但此处是本地磁盘后端,用委托而非继承,因为\n * `DiskPersistence` 承载了 `list`/`listPaginated` 等 PanelState 不需要的语义,\n * 直接继承会泄漏与 `EntityPersistenceShape` 契约不匹配的方法。\n */\nexport class DiskPanelStatePersistence implements PanelStatePersistence {\n private readonly store: PanelStateDiskStore\n\n constructor(options: { baseDir: string }) {\n this.store = new PanelStateDiskStore({\n baseDir: options.baseDir,\n extension: '.json',\n })\n }\n\n async save(snapshot: PanelStateSnapshot): Promise<void> {\n return this.store.save(snapshot)\n }\n\n async load(id: string): Promise<PanelStateSnapshot | null> {\n return this.store.load(id)\n }\n\n async delete(id: string): Promise<boolean> {\n return this.store.delete(id)\n }\n}\n\n/**\n * RFC-345:PanelState 内存后端(影子模式)。面板态只活在进程内存,退出即释放。\n */\nexport function createInMemoryPanelStatePersistence(): PanelStatePersistence {\n return new InMemoryEntityPersistence<PanelStateSnapshot>((s) => s.sessionId)\n}\n","import { DiskPersistence } from './disk-persistence'\nimport { InMemoryEntityPersistence } from './in-memory-entity-persistence'\nimport type { EntityPersistenceShape } from './types'\n\n// ---------------------------------------------------------------------------\n// RFC-338 D1:粘贴 chip 原文缓存的持久化契约。\n//\n// **为何独立于 `PanelStateSnapshot`**(RFC-338 D1,二轮评审 F3):\n// `PanelStateStore` 的每个 setter 都是「全量 load → 改一个字段 → 全量 save」\n// (见 panel-state-store.ts 的 setTodoList/setEditedFiles 等逐个同构),而\n// `setEditedFiles` 是**每次文件编辑即触发**的高频路径(cli 侧 editFileCallback\n// 每次编辑就落一次盘)。若把最大 8MB 的粘贴负载塞进同一份 JSON,每次 tool_use\n// 编辑文件都要反序列化 + 重序列化这 8MB —— 把一条与 paste 完全无关的热路径\n// 拖慢数个量级。故 paste 独立信封、独立文件、独立生命周期。\n//\n// **为何仍在 `panel-state/` 目录下**:复用既有落盘根与 migrate 打包语义\n// (`otto migrate export` 递归拷贝 panel-state/,paste 随之搬迁 —— RFC-338 Q2\n// 用户定稿:随包 + 用户可见处显式声明)。不新建第四条 per-session 路径根。\n// ---------------------------------------------------------------------------\n\n/**\n * 持久化粘贴条目(RFC-338)。\n *\n * **不含时间戳/序号字段**:LRU 顺序由 {@link PasteStateSnapshot.entries} 的\n * **数组顺序**表达(头 = 最久未访问,尾 = 最近访问),与内存侧 `Map` 的插入顺序\n * 语义一一对应(RFC-338 D2)。引入独立 `accessSeq` 会与 Map 顺序构成双真源;\n * 引入 `Date.now()` 则违反引擎核心的确定性重放约束。\n */\nexport interface PasteStateEntry {\n /** 占位符 `[@paste:#N]` 中的 N。 */\n id: number\n /** 折叠前的原文。 */\n text: string\n /** utf-8 字节数(容量闸的另一维度,构造时算好避免重复计算)。 */\n byteLength: number\n}\n\n/**\n * RFC-338:paste 缓存独立信封。\n *\n * `sessionId` 既是业务关联键也是持久化主键——按 sessionId 一对一存取,\n * 从不翻页/列表查询,故契约用 `EntityPersistenceShape`(同 PanelState 的判断)。\n */\nexport interface PasteStateSnapshot {\n sessionId: string\n /** 有序数组 = LRU 顺序(头 = 最久未访问)。 */\n entries: PasteStateEntry[]\n /**\n * id 分配游标,**单调不回退**(RFC-338 重要事项规则 1,红线)。\n *\n * 违反后果不是「chip 失效」而是「chip 静默展开成另一份内容并发给模型」——\n * 内存态重启后 store 为空、旧 chip 判失效是**安全的失败**;持久化态下若\n * 该游标回退,新粘贴会复用旧 id,历史里的 `[@paste:#7]` 就会指向错误内容。\n */\n nextId: number\n}\n\n/** RFC-338:paste 缓存持久化契约(无分页三方法,同 PanelStatePersistence)。 */\nexport interface PasteStatePersistence extends EntityPersistenceShape<PasteStateSnapshot> {}\n\n// ---------------------------------------------------------------------------\n// 具体实现:本地磁盘后端\n// ---------------------------------------------------------------------------\n\n/**\n * 内部委托类——继承 `DiskPersistence<PasteStateSnapshot>` 仅为复用其\n * save/load/delete 的磁盘 I/O(崩溃安全原子写 temp→fsync→rename→fsync(dir)、codec),\n * 不暴露 list/listPaginated。\n */\nclass PasteStateDiskStore extends DiskPersistence<PasteStateSnapshot> {\n protected extractId(snapshot: PasteStateSnapshot): string {\n return snapshot.sessionId\n }\n}\n\n/**\n * RFC-338:paste 缓存本地磁盘持久化实现。\n *\n * **落盘位置是 `panel-state/` 下的独立子目录 `paste/`**,不是与面板态同级的\n * `<sessionId>.paste.json`。原因(实现期实测):`DiskPersistence` 的\n * `list()`/`listPaginated()` 用 `endsWith(extension)` 过滤、用\n * `slice(0, -extension.length)` 反解 id —— 若 paste 用 `.paste.json` 后缀与面板态\n * 同目录并存,面板态那侧以 `.json` 过滤会**连带匹配到 paste 文件**,且把 id\n * 反解成 `<sessionId>.paste`(实测 `'s1.paste.json'.endsWith('.json') === true`)。\n * 独立子目录让两者的文件名空间物理隔离,两侧都用标准 `.json` 后缀,无需依赖\n * 「当前恰好没人调用 list()」这种脆弱前提。\n */\nexport class DiskPasteStatePersistence implements PasteStatePersistence {\n private readonly store: PasteStateDiskStore\n\n constructor(options: { baseDir: string }) {\n this.store = new PasteStateDiskStore({\n baseDir: options.baseDir,\n extension: '.json',\n })\n }\n\n async save(snapshot: PasteStateSnapshot): Promise<void> {\n return this.store.save(snapshot)\n }\n\n async load(id: string): Promise<PasteStateSnapshot | null> {\n return this.store.load(id)\n }\n\n async delete(id: string): Promise<boolean> {\n return this.store.delete(id)\n }\n}\n\n// ---------------------------------------------------------------------------\n// 纯函数:快照规范化(hydrate 的守门人)\n// ---------------------------------------------------------------------------\n\n/**\n * RFC-338 重要事项规则 1(红线):从**不可信**的持久化快照推导安全的 `nextId`。\n *\n * 取 `max(1, 快照声明的 nextId, 全部 entry.id + 1)` —— 三者取最大保证游标既不\n * 回退、也不与任何既存 id 碰撞。\n *\n * **边界(二轮评审 F4,已实测)**:`Math.max(...[])` 返回 `-Infinity`,故绝不能\n * 写成 `Math.max(...ids) + 1` 的裸形式 —— entries 为空时会得到 `-Infinity`,\n * 后续 `nextId++` 全程为 `-Infinity`,占位符变成 `[@paste:#-Infinity]`。\n * 起始种子 `1` 同时兜住了空数组与 `nextId` 缺失两种情况。\n *\n * @param snapshot 可能损坏/缺字段/被手工编辑过的快照\n */\nexport function resolvePasteNextId(snapshot: unknown): number {\n const source = (typeof snapshot === 'object' && snapshot !== null ? snapshot : {}) as {\n entries?: unknown\n nextId?: unknown\n }\n const declared = typeof source.nextId === 'number' && Number.isFinite(source.nextId)\n ? Math.floor(source.nextId)\n : 0\n let next = Math.max(1, declared)\n // `entries` 可能是任意类型(手工编辑/旧格式/函数),只有真数组才迭代——\n // 直接 `for...of` 非可迭代值会抛 TypeError,那会让这个「守门人」自己变成崩溃源。\n const entries = Array.isArray(source.entries) ? source.entries : []\n for (const entry of entries) {\n const id = (entry as { id?: unknown } | null)?.id\n if (typeof id === 'number' && Number.isFinite(id)) {\n next = Math.max(next, Math.floor(id) + 1)\n }\n }\n return next\n}\n\n/**\n * RFC-338 §7:把不可信快照规范化为可用状态(fail-open)。\n *\n * 丢弃非法 entry(缺字段/类型错/id 非有限数)而非整份拒绝——用户不该因为一条\n * 坏记录丢掉整个会话的粘贴缓存。`byteLength` 一律按 `text` 重算,不信任存储值\n * (手工编辑或旧版本写入的值可能与实际不符,直接影响容量闸判断)。\n *\n * 无论 entries 如何裁剪,`nextId` 都基于**原始**快照推导——防止「坏 entry 被丢弃\n * → id 空间看似空出 → 新粘贴复用该 id → 历史 chip 错展开」的绕行路径。\n */\nexport function normalizePasteSnapshot(\n sessionId: string,\n raw: unknown,\n): PasteStateSnapshot {\n const source = (typeof raw === 'object' && raw !== null ? raw : {}) as {\n entries?: unknown\n nextId?: unknown\n }\n const rawEntries = Array.isArray(source.entries) ? source.entries : []\n\n const entries: PasteStateEntry[] = []\n const seen = new Set<number>()\n for (const candidate of rawEntries) {\n if (typeof candidate !== 'object' || candidate === null) continue\n const { id, text } = candidate as { id?: unknown; text?: unknown }\n if (typeof id !== 'number' || !Number.isFinite(id)) continue\n if (typeof text !== 'string') continue\n const normalizedId = Math.floor(id)\n // 重复 id:保留首次出现的(数组序靠前 = 更久未访问),后续同 id 丢弃。\n // 不可能有两份合法内容共用一个 id,出现即数据损坏,保守取其一。\n if (seen.has(normalizedId)) continue\n seen.add(normalizedId)\n entries.push({\n id: normalizedId,\n text,\n byteLength: Buffer.byteLength(text, 'utf-8'),\n })\n }\n\n return {\n sessionId,\n entries,\n // 注意:基于 source(原始)而非 entries(已裁剪)推导,见上方 doc。\n nextId: resolvePasteNextId(source),\n }\n}\n\n/**\n * RFC-345:paste 缓存内存后端(影子模式)。粘贴态只活在进程内存,退出即释放。\n */\nexport function createInMemoryPasteStatePersistence(): PasteStatePersistence {\n return new InMemoryEntityPersistence<PasteStateSnapshot>((s) => s.sessionId)\n}\n","import { RemotePersistenceError, RemoteSnapshotConflictError } from './errors'\nimport type { PaginatedResult, Codec } from './types'\n\nexport interface RemotePersistenceOptions<S = unknown> {\n baseUrl: string\n /**\n * workspace 分区键(default='default');客户端 URL 拼接 wsKey。\n * 支持惰性 getter(`() => string | undefined`)——修复(N3,resume/workspace 关联审计):\n * 消费方(如 App)在构造期就创建持久化实例,而 workspaceRef.key 要到 App.start() 异步\n * resolveLocal 完成后才有真实值,与 RelationalSessionPersistence.workspaceKey 的既有\n * 惰性 getter 模式同源同因(同一类时序错位,见 Bug1 修复记录)。静态字符串仍受支持\n * (组件测试 / 无 workspace 概念场景)。\n */\n wsKey?: string | (() => string | undefined)\n getAuth: () => Promise<{ token: string }>\n getHeaders?: () => Promise<Record<string, string>>\n fetch: typeof globalThis.fetch\n timeoutMs: number\n defaultPageSize?: number\n defaultSortBy?: string\n codec?: Codec<S>\n}\n\nexport abstract class RemotePersistence<S> {\n protected readonly baseUrl: string\n /** workspace 分区键解析器,URL 路径前缀;缺省 'default'。每次用时惰性求值。 */\n private readonly resolveWsKey: () => string\n protected get wsKey(): string {\n return this.resolveWsKey()\n }\n protected readonly getAuth: () => Promise<{ token: string }>\n protected readonly getHeaders: () => Promise<Record<string, string>>\n protected readonly fetch: typeof globalThis.fetch\n protected readonly timeoutMs: number\n protected readonly defaultPageSize: number\n protected readonly defaultSortBy: string\n protected readonly codec: Codec<S>\n\n /**\n * 错误文案用的服务标签;通用基类默认中性名,领域子类覆盖(如 RemoteSessionPersistence → \"会话服务\")。\n * 修正:基类原硬编码\"会话服务器\",但它同时被通用 HttpPersistence 继承 → 非会话用途报错误导。\n */\n protected get serviceLabel(): string {\n return '远端持久化服务'\n }\n /** 错误文案用的端点配置项名;领域子类覆盖(如 '--session-url')。 */\n protected get endpointFlag(): string {\n return '--persistence-url'\n }\n\n constructor(options: RemotePersistenceOptions<S>) {\n this.baseUrl = options.baseUrl.replace(/\\/+$/, '')\n const wk = options.wsKey\n this.resolveWsKey = typeof wk === 'function' ? () => wk() || 'default' : () => wk || 'default'\n\n this.getAuth = options.getAuth\n this.getHeaders = options.getHeaders ?? (async () => ({}))\n this.fetch = options.fetch\n\n this.timeoutMs = options.timeoutMs\n this.defaultPageSize = options.defaultPageSize ?? 20\n this.defaultSortBy = options.defaultSortBy ?? 'updatedAt'\n this.codec = options.codec ?? {\n encode: (snapshot: S) => JSON.stringify(snapshot),\n decode: (payload: string) => JSON.parse(payload) as S,\n contentType: 'application/json',\n }\n }\n\n /** wsKey 路径拼接:`/{wsKey}/{path}`。 */\n private wsPath(id: string): string {\n return `/${encodeURIComponent(this.wsKey)}/${encodeURIComponent(id)}`\n }\n\n /** wsKey 路径 list: `/{wsKey}`。 */\n protected wsListPath(): string {\n return `/${encodeURIComponent(this.wsKey)}`\n }\n\n protected abstract extractId(snapshot: S): string\n\n /**\n * 无条件整份 PUT(兼容旧调用方)。新调用方应改用 `saveWithRevision()` 以获得 CAS 保护。\n */\n async save(snapshot: S): Promise<void> {\n await this.request(this.wsPath(this.extractId(snapshot)), {\n method: 'PUT',\n body: this.codec.encode(snapshot),\n })\n }\n\n /**\n * CAS 写入:带 `If-Match: \"<expectedRevision>\"` 发起 PUT。\n * - 成功:返回服务端分配的新 revision。\n * - 冲突(409):抛 `RemoteSnapshotConflictError`,`info.revision` 为服务端当前 revision。\n * - 未提供 expectedRevision:等价于无条件写,服务端不比对;返回新 revision。\n *\n * `expectedRevision=0` 显式表示\"期望文档尚不存在\"(新建场景)。\n */\n async saveWithRevision(snapshot: S, expectedRevision?: number): Promise<number> {\n const headers: Record<string, string> = {}\n if (expectedRevision !== undefined) {\n headers['If-Match'] = String(expectedRevision)\n }\n const response = await this.request(\n this.wsPath(this.extractId(snapshot)),\n { method: 'PUT', body: this.codec.encode(snapshot) },\n headers,\n )\n\n const etag = response.headers.get('ETag')\n const revision = etag ? Number(etag.replace(/\"/g, '')) : NaN\n return revision\n }\n\n /**\n * 读取快照与其 revision。revision 供下次 `saveWithRevision()` 的 `expectedRevision` 使用。\n * 文档不存在返回 null(revision 也为 null)。\n */\n async loadWithRevision(id: string): Promise<{ snapshot: S; revision: number } | null> {\n const response = await this.request(this.wsPath(id), { method: 'GET' })\n if (response.status === 404) {\n return null\n }\n const text = await response.text()\n let snapshot: S\n try {\n snapshot = this.codec.decode(text)\n } catch (err) {\n throw new RemotePersistenceError(\n `Remote persistence decode failed: ${this.wsPath(id)}`,\n response.status,\n { cause: err },\n )\n }\n const etag = response.headers.get('ETag')\n const revision = etag ? Number(etag.replace(/\"/g, '')) : 0\n return { snapshot, revision }\n }\n\n async load(id: string): Promise<S | null> {\n const result = await this.loadWithRevision(id)\n return result?.snapshot ?? null\n }\n\n async delete(id: string): Promise<boolean> {\n const response = await this.request(this.wsPath(id), {\n method: 'DELETE',\n })\n\n return response.ok\n }\n\n async list(): Promise<string[]> {\n const response = await this.request(this.wsListPath(), { method: 'GET' })\n const data = (await response.json()) as { ids: string[] }\n return data.ids\n }\n\n async listPaginated(options: {\n page: number\n pageSize?: number\n sortBy?: string\n order?: 'asc' | 'desc'\n }): Promise<PaginatedResult<string>> {\n const { page, order = 'desc' } = options\n const sortBy = options.sortBy ?? this.defaultSortBy\n const pageSize = options.pageSize ?? this.defaultPageSize\n\n const params = new URLSearchParams({\n page: String(page),\n pageSize: String(pageSize),\n sortBy,\n order,\n })\n\n const response = await this.request(`${this.wsListPath()}?${params.toString()}`, {\n method: 'GET',\n })\n\n return response.json() as Promise<PaginatedResult<string>>\n }\n\n protected async request(\n path: string,\n init: { method: string; body?: string },\n extraHeaders?: Record<string, string>,\n ): Promise<Response> {\n const auth = await this.getAuth()\n\n const headers: Record<string, string> = {\n ...(await this.getHeaders()),\n ...(auth.token ? { Authorization: `Bearer ${auth.token}` } : {}),\n Accept: 'application/json',\n ...extraHeaders,\n }\n\n if (init.body) {\n headers['Content-Type'] = this.codec.contentType ?? 'application/json'\n }\n\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), this.timeoutMs)\n\n const url = `${this.baseUrl}${path}`\n\n try {\n const response = await this.fetch(url, {\n method: init.method,\n headers,\n body: init.body,\n signal: controller.signal,\n })\n\n if (response.status === 409) {\n // CAS 冲突:解析服务端返回的当前 revision,抛特化错误供调用方 reload/rebase。\n let conflictRevision: number | null = null\n try {\n const body = (await response.clone().json()) as { revision?: number | null }\n conflictRevision = typeof body.revision === 'number' ? body.revision : null\n } catch {\n // 非法 JSON body 时 revision 保持 null;不掩盖原始 409 事实。\n }\n throw new RemoteSnapshotConflictError(\n `Remote persistence CAS conflict: ${this.wsPath(path.split('/').pop() ?? '')}`,\n { revision: conflictRevision },\n )\n }\n\n if (!response.ok && response.status !== 404) {\n throw new RemotePersistenceError(\n `Remote persistence API error: ${response.status} ${response.statusText}`,\n response.status,\n )\n }\n\n return response\n } catch (err) {\n if (err instanceof RemoteSnapshotConflictError) {\n throw err\n }\n if (err instanceof RemotePersistenceError) {\n throw err\n }\n\n if (err instanceof DOMException && err.name === 'AbortError') {\n throw new RemotePersistenceError(\n `无法连接到${this.serviceLabel}(请求超时 ${this.timeoutMs / 1000}s):${init.method} ${url}\\n` +\n `提示:请确认${this.serviceLabel}是否已启动、${this.endpointFlag} 地址与端口是否正确。`,\n 0,\n { cause: err },\n )\n }\n\n const causeCode = (err as Error & { cause?: { code?: string } })?.cause?.code\n const codeHint = causeCode\n ? causeCode === 'ECONNREFUSED'\n ? '(连接被拒绝)'\n : causeCode === 'ENOTFOUND'\n ? '(DNS 解析失败,地址不存在)'\n : causeCode === 'ECONNRESET'\n ? '(连接被重置)'\n : causeCode === 'ETIMEDOUT'\n ? '(连接超时)'\n : causeCode === 'CERT_HAS_EXPIRED'\n ? '(TLS 证书已过期)'\n : causeCode === 'DEPTH_ZERO_SELF_SIGNED_CERT'\n ? '(自签名证书被拒)'\n : `(${causeCode})`\n : ''\n\n const detail = err instanceof Error ? err.message : String(err)\n\n throw new RemotePersistenceError(\n `无法连接到${this.serviceLabel}:${init.method} ${url}\\n` +\n `原因:${detail}${codeHint}\\n` +\n `提示:请确认${this.serviceLabel}是否已启动、${this.endpointFlag} 地址与端口是否正确。`,\n 0,\n { cause: err },\n )\n } finally {\n clearTimeout(timeout)\n }\n }\n}\n","import { DiskPersistence } from './disk-persistence'\nimport type { Persistence, Snapshot, Codec } from './types'\n\nexport interface FilePersistenceOptions<T = unknown> {\n baseDir: string\n extension?: string\n codec?: Codec<Snapshot<T>>\n}\n\nexport class FilePersistence<T = unknown>\n extends DiskPersistence<Snapshot<T>>\n implements Persistence<T>\n{\n constructor(options: FilePersistenceOptions<T>) {\n super({ baseDir: options.baseDir, extension: options.extension, codec: options.codec })\n }\n\n protected extractId(snapshot: Snapshot<T>): string {\n return snapshot.id\n }\n}\n","import { mkdirSync } from 'node:fs'\nimport { dirname } from 'node:path'\nimport Database from 'better-sqlite3'\n\ntype CachedDb = { db: Database.Database; refs: number; migrationTarget: number }\n\nconst cache = new Map<string, CachedDb>()\n\n// 目标 schema 版本不再用全局常量,改由注入的 migration 段派生(targetVersion,RFC-074 M5-02a)——\n// 每个消费方的库版本 = 它自己段的最大 version,避免通用内核硬编码会话/blob 的合并版本号。\n\n/** 单次 schema 迁移。`version` 必须连续从小到大。 */\nexport interface Migration {\n /** 迁移目标版本号(1-based);`up` 执行后 db 的 user_version 变为该值。 */\n version: number\n /** 前向迁移 DDL。幂等(建议用 `CREATE TABLE IF NOT EXISTS`)。 */\n up: (db: Database.Database) => void\n}\n\n/**\n * blob 快照后端的 schema 段(SqlitePersistence:snapshots + change_log)。\n * RFC-074 M5-02a:从单一 `migrations[]` 拆出,使 blob 后端只建自己需要的表。\n */\nexport const BLOB_MIGRATIONS: Migration[] = [\n {\n version: 1,\n up: (db) => {\n db.exec(`\n -- snapshots (SqlitePersistence)\n CREATE TABLE IF NOT EXISTS snapshots (\n ns TEXT NOT NULL,\n id TEXT NOT NULL,\n version INTEGER NOT NULL DEFAULT 1,\n data TEXT NOT NULL,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL,\n tags TEXT NOT NULL DEFAULT '[]',\n PRIMARY KEY (ns, id)\n );\n CREATE INDEX IF NOT EXISTS idx_snapshots_ns_updated\n ON snapshots(ns, updated_at DESC);\n CREATE INDEX IF NOT EXISTS idx_snapshots_ns_created\n ON snapshots(ns, created_at DESC);\n\n -- change_log (SqlitePersistence)\n CREATE TABLE IF NOT EXISTS change_log (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n ns TEXT NOT NULL,\n doc_id TEXT NOT NULL,\n op TEXT NOT NULL,\n ts INTEGER NOT NULL\n );\n CREATE INDEX IF NOT EXISTS idx_change_log_ns_ts\n ON change_log(ns, ts);\n `)\n },\n },\n {\n version: 2,\n up: (db) => {\n db.exec(`\n -- append_log(RFC-089 M28 事件溯源日志表结构;消费实现 SqliteAppendLog 已删除——\n -- 终局架构 review 2026-07-10 P1-7 确认零生产接线,迁移本身保留不动以维持已发布\n -- migration 历史的不可变性,仅此注释更新去掉对已删除类名的引用)\n CREATE TABLE IF NOT EXISTS append_log (\n stream_id TEXT NOT NULL,\n seq INTEGER NOT NULL,\n payload TEXT NOT NULL,\n PRIMARY KEY (stream_id, seq)\n );\n `)\n },\n },\n]\n\n// 关系化会话 schema 段(SESSION_MIGRATIONS)已随 SessionRepository 迁至 @x-otto/session\n// (RFC-074 M5-02b,根治 R-LAYER:通用存储内核不再硬编码 session 领域 schema)。\n// 会话库(sessions.db)由 SqliteSessionRepository 显式注入其 SESSION_MIGRATIONS;\n// 二者用不同 db 文件、从不共建同库,故此处无需 compose——acquireDb 默认段收敛为 BLOB_MIGRATIONS。\n\nconst migrated = new Set<string>()\n\n/** 从 migration 段派生目标 schema 版本(= 最大 version)。 */\nfunction targetVersion(migrationSet: Migration[]): number {\n return migrationSet.reduce((max, m) => Math.max(max, m.version), 0)\n}\n\nfunction applyMigrations(db: Database.Database, dbPath: string, migrationSet: Migration[]): void {\n const target = targetVersion(migrationSet)\n\n if (dbPath === ':memory:') {\n const cv = db.pragma('user_version', { simple: true }) as number\n if (cv === 0) {\n for (const m of migrationSet) m.up(db)\n db.pragma(`user_version = ${target}`)\n }\n return\n }\n\n if (migrated.has(dbPath)) return\n\n const cv = db.pragma('user_version', { simple: true }) as number\n\n if (cv === 0) {\n for (const m of migrationSet) m.up(db)\n } else if (cv < target) {\n for (const m of migrationSet) {\n if (m.version > cv) m.up(db)\n }\n } else if (cv > target) {\n throw new Error(\n `DB at ${dbPath} has schema v${cv} > code v${target}. ` +\n 'Downgrade not supported. Upgrade otto first.',\n )\n }\n\n db.pragma(`user_version = ${target}`)\n migrated.add(dbPath)\n}\n\nexport function acquireDb(\n dbPath: string,\n wal = true,\n migrationSet: Migration[] = BLOB_MIGRATIONS,\n): Database.Database {\n if (dbPath === ':memory:') {\n const db = new Database(':memory:')\n applyMigrations(db, dbPath, migrationSet)\n return db\n }\n\n const existing = cache.get(dbPath)\n if (existing) {\n // fail-fast:同一 db 路径被注入不同 migration 段(不同 target 版本)→ 缓存连接的 schema 与\n // 本次请求不符,静默复用会导致难排查的\"表不存在/列缺失\"。当前 blob/session 用不同 db 文件\n // 永不撞此路径(见 sqlite-db-cache 头注),此守卫是防御性护栏(RFC-074 backlog)。\n const want = targetVersion(migrationSet)\n if (existing.migrationTarget !== want) {\n throw new Error(\n `acquireDb cache conflict at ${dbPath}: cached schema v${existing.migrationTarget} ` +\n `but requested migrationSet targets v${want}. 同一 db 路径不可注入不同 migration 段。`,\n )\n }\n existing.refs++\n return existing.db\n }\n // 启动健壮性:DB 目录不存在时 better-sqlite3 直接抛 \"Cannot open database because the directory\n // does not exist\"(fresh HOME/新环境会中招)。打开前自建目录,幂等。\n mkdirSync(dirname(dbPath), { recursive: true })\n const db = new Database(dbPath)\n if (wal) {\n db.pragma('journal_mode = WAL')\n\n db.pragma('synchronous = NORMAL')\n db.pragma('busy_timeout = 5000')\n }\n\n applyMigrations(db, dbPath, migrationSet)\n cache.set(dbPath, { db, refs: 1, migrationTarget: targetVersion(migrationSet) })\n return db\n}\n\nexport function releaseDb(db: Database.Database): void {\n if (db.name === ':memory:') {\n db.close()\n return\n }\n\n const path = db.name\n const entry = cache.get(path)\n if (!entry) {\n return\n }\n entry.refs--\n if (entry.refs <= 0) {\n db.close()\n cache.delete(path)\n }\n}\n","import type Database from 'better-sqlite3'\nimport { acquireDb, releaseDb, BLOB_MIGRATIONS } from './sqlite-db-cache'\nimport type { PaginatedResult, Persistence, Snapshot, Codec } from './types'\nimport type { ChangeLogEntry } from './change-log'\n\nexport interface SqlitePersistenceOptions<T = unknown> {\n /** SQLite 数据库文件路径。用 ':memory:' 做测试。 */\n dbPath: string\n /** 是否启用 WAL 模式(默认 true)。 */\n wal?: boolean\n /** 表内 namespace 标识(用于跨 ns 共享单个 db 文件)。 */\n namespace: string\n /** 默认每页大小(默认 20)。 */\n defaultPageSize?: number\n /** snapshots 表的 codec(默认 JSON)。 */\n codec?: Codec<Snapshot<T>>\n}\n\nconst DEFAULT_PAGE_SIZE = 20\n\n/**\n * SQLite 快照后端(M28-01)。\n *\n * 通过通用 `snapshots` 表实现 `Persistence<T>` 接口——单 db 文件多 namespace 共享,\n * WAL 模式支持 1 写者 + N 读者并发。每次 save 自动写 `change_log` 行供 SSE 轮询。\n *\n * 通过 `sqlite-db-cache` 的单一连接缓存(acquireDb/releaseDb)与其他 consumer 共享同一 db 文件,\n * 避免 WAL 锁冲突。DDL 由 `acquireDb` 的 `applyMigrations` 统一管理(M60-02);本类不再自建表。\n */\nexport class SqlitePersistence<T = unknown> implements Persistence<T> {\n private readonly db: Database.Database\n private readonly ns: string\n private readonly defaultPageSize: number\n private readonly codec: Codec<Snapshot<T>>\n\n constructor(options: SqlitePersistenceOptions<T>) {\n this.ns = options.namespace\n this.defaultPageSize = options.defaultPageSize ?? DEFAULT_PAGE_SIZE\n this.codec = options.codec ?? {\n encode: (snapshot: Snapshot<T>) => JSON.stringify(snapshot),\n decode: (payload: string) => JSON.parse(payload) as Snapshot<T>,\n contentType: 'application/json',\n }\n\n this.db = acquireDb(options.dbPath, options.wal !== false, BLOB_MIGRATIONS)\n }\n\n /** 释放引用;相同 db 路径上所有实例都释放后才关闭物理连接。 */\n close(): void {\n releaseDb(this.db)\n }\n\n async save(snapshot: Snapshot<T>): Promise<void> {\n const id = snapshot.id\n const version = snapshot.version ?? 1\n const data = this.codec.encode(snapshot)\n const now = snapshot.metadata?.updatedAt ?? Date.now()\n const createdAt = snapshot.metadata?.createdAt ?? now\n const tags = JSON.stringify(snapshot.metadata?.tags ?? [])\n\n const upsert = this.db.prepare(`\n INSERT INTO snapshots (ns, id, version, data, created_at, updated_at, tags)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT (ns, id) DO UPDATE SET\n version = excluded.version,\n data = excluded.data,\n updated_at = excluded.updated_at,\n tags = excluded.tags\n `)\n const logInsert = this.db.prepare(\n `INSERT INTO change_log (ns, doc_id, op, ts) VALUES (?, ?, 'save', ?)`,\n )\n\n this.db.transaction(() => {\n upsert.run(this.ns, id, version, data, createdAt, now, tags)\n logInsert.run(this.ns, id, now)\n })()\n }\n\n async load(id: string): Promise<Snapshot<T> | null> {\n const row = this.db\n .prepare(`SELECT data FROM snapshots WHERE ns = ? AND id = ?`)\n .get(this.ns, id) as { data: string } | undefined\n\n if (!row) {\n return null\n }\n return this.codec.decode(row.data)\n }\n\n async delete(id: string): Promise<boolean> {\n const del = this.db.prepare(`DELETE FROM snapshots WHERE ns = ? AND id = ?`)\n const logInsert = this.db.prepare(\n `INSERT INTO change_log (ns, doc_id, op, ts) VALUES (?, ?, 'delete', ?)`,\n )\n\n return this.db.transaction(() => {\n const result = del.run(this.ns, id)\n if (result.changes > 0) {\n logInsert.run(this.ns, id, Date.now())\n return true\n }\n return false\n })()\n }\n\n async list(): Promise<string[]> {\n const rows = this.db\n .prepare(`SELECT id FROM snapshots WHERE ns = ? ORDER BY updated_at DESC`)\n .all(this.ns) as { id: string }[]\n\n return rows.map((r) => r.id)\n }\n\n async listPaginated(options: {\n page: number\n pageSize?: number\n sortBy?: string\n order?: 'asc' | 'desc'\n }): Promise<PaginatedResult<string>> {\n const { page, order = 'desc' } = options\n const sortBy = options.sortBy ?? 'updated_at'\n const pageSize = options.pageSize ?? this.defaultPageSize\n\n const col = sortBy === 'created_at' ? 'created_at' : 'updated_at'\n const dir = order === 'asc' ? 'ASC' : 'DESC'\n\n const { count } = this.db\n .prepare(`SELECT COUNT(*) as count FROM snapshots WHERE ns = ?`)\n .get(this.ns) as { count: number }\n\n const total = count\n const totalPages = Math.max(1, Math.ceil(total / pageSize))\n const safePage = Math.max(0, Math.min(page, totalPages - 1))\n const offset = safePage * pageSize\n\n const rows = this.db\n .prepare(`SELECT id FROM snapshots WHERE ns = ? ORDER BY ${col} ${dir} LIMIT ? OFFSET ?`)\n .all(this.ns, pageSize, offset) as { id: string }[]\n\n return {\n items: rows.map((r) => r.id),\n total,\n page: safePage,\n pageSize,\n totalPages,\n hasNext: safePage < totalPages - 1,\n hasPrevious: safePage > 0,\n }\n }\n\n /**\n * 查询某个 ns 在 since (unix ms) 起的变更日志。\n * 用 `ts >= since`(非 `>`)+ 单调 `id` 次序:同毫秒内后到的变更不会因游标落在该 ms 被跳过\n * (消费方按 change_log.id 去重已产出项)。次序 `ts ASC, id ASC` 保证 id 单调可用作去重游标。\n */\n getChangeLog(ns: string, since: number, limit = 100): ChangeLogEntry[] {\n return this.db\n .prepare(\n `SELECT id, ns, doc_id, op, ts FROM change_log WHERE ns = ? AND ts >= ? ORDER BY ts ASC, id ASC LIMIT ?`,\n )\n .all(ns, since, limit) as ChangeLogEntry[]\n }\n\n /** 增量拉取自 since 后变更的文档 ID 列表 + 最新游标(M28-05 sync pull 优化路径)。 */\n getChangesSince(ns: string, since: number, ids?: string[]): { ids: string[]; cursor: number } {\n let query = `SELECT id, updated_at FROM snapshots WHERE ns = ? AND updated_at > ?`\n const params: unknown[] = [ns, since]\n\n if (ids && ids.length > 0) {\n query += ` AND id IN (${ids.map(() => '?').join(',')})`\n params.push(...ids)\n }\n query += ` ORDER BY updated_at ASC`\n\n const rows = this.db.prepare(query).all(...params) as { id: string; updated_at: number }[]\n const maxRow =\n rows.length > 0\n ? rows.reduce((max, r) => Math.max(max, r.updated_at), 0)\n : this.getLatestCursor(ns)\n\n return {\n ids: rows.map((r) => r.id),\n cursor: maxRow,\n }\n }\n\n /** 查询 since 后被删除的文档 ID 列表(M28-05 sync pull delete 标记)。 */\n getDeletedSince(ns: string, since: number): string[] {\n const rows = this.db\n .prepare(`SELECT doc_id FROM change_log WHERE ns = ? AND op = 'delete' AND ts > ?`)\n .all(ns, since) as { doc_id: string }[]\n return rows.map((r) => r.doc_id)\n }\n\n /** 获取某个 ns 的最新 updated_at 时间戳(供 SQL cursor 初始化)。 */\n private getLatestCursor(ns: string): number {\n const row = this.db\n .prepare(`SELECT MAX(updated_at) as ts FROM snapshots WHERE ns = ?`)\n .get(ns) as { ts: number | null } | undefined\n return row?.ts ?? 0\n }\n\n /** 获取某个 ns 的最新变更时间戳(供 cursor 初始化)。 */\n getLatestChangeTs(ns: string): number {\n const row = this.db.prepare(`SELECT MAX(ts) as ts FROM change_log WHERE ns = ?`).get(ns) as\n | { ts: number | null }\n | undefined\n return row?.ts ?? 0\n }\n\n /** SQLite 完整性诊断信息(M34-02 doctor 使用)。 */\n getInfo(): {\n walEnabled: boolean\n snapshots: number\n changeLogEntries: number\n sessions: number\n integrity: string\n } {\n const wal = this.db.pragma('journal_mode') as unknown as string\n const [{ count: snapshots }] = this.db\n .prepare('SELECT COUNT(*) as count FROM snapshots')\n .all() as [{ count: number }]\n const [{ count: changeLog }] = this.db\n .prepare('SELECT COUNT(*) as count FROM change_log')\n .all() as [{ count: number }]\n const [{ count: sessions }] = this.db\n .prepare(\"SELECT COUNT(*) as count FROM snapshots WHERE ns = 'sessions'\")\n .all() as [{ count: number }]\n const integrityRows = this.db.pragma('integrity_check') as Record<string, string>[]\n const integrityOk = integrityRows.length === 1 && integrityRows[0]?.integrity_check === 'ok'\n\n return {\n walEnabled: wal === 'wal',\n snapshots,\n changeLogEntries: changeLog,\n sessions,\n integrity: integrityOk ? 'ok' : `fail: ${JSON.stringify(integrityRows)}`,\n }\n }\n}\n","import { FilePersistence } from './file-persistence'\nimport { MemoryPersistence } from './memory-persistence'\nimport { SqlitePersistence } from './sqlite-persistence'\n\nimport type { Persistence, StorageOptions } from './types'\n\n/**\n * 统一存储工厂:按 `options.type` 直构造内置后端(memory/file/http/sqlite)。\n *\n * M91-02(RFC-057):删 StorageBackendRegistry(零外部注册者的 Map 间接层),改内联 switch。\n * M93-01(RFC-057 §3 D7):`StorageOptions` 为穷尽判别联合,switch 按 type 窄化——去工厂边界\n * `as unknown as`,恢复编译期保护(漏 case / 字段类型不符均被 typecheck 拦)。\n */\nexport function createPersistence<T = unknown>(options: StorageOptions<T>): Persistence<T> {\n switch (options.type) {\n case 'memory':\n return new MemoryPersistence() as Persistence<T>\n case 'file':\n return new FilePersistence({\n baseDir: options.baseDir,\n extension: options.extension,\n codec: options.codec,\n }) as Persistence<T>\n case 'sqlite':\n return new SqlitePersistence({\n dbPath: options.dbPath,\n wal: options.wal,\n namespace: options.namespace,\n defaultPageSize: options.defaultPageSize,\n codec: options.codec,\n }) as Persistence<T>\n default:\n throw new Error(`Unsupported storage type: ${(options as { type: string }).type}`)\n }\n}\n","import { join } from 'node:path'\nimport { constants } from 'node:fs'\nimport { StringDecoder } from 'node:string_decoder'\nimport { mkdir, open, unlink } from 'node:fs/promises'\nimport { PersistenceError } from './errors'\n\n/**\n * append-only 日志原语(取向 B,与 snapshot 取向正交)。\n *\n * 与 `Persistence<Snapshot<T>>`(一份最新状态的 CRUD)不同,`AppendLog<E>` 表达\n * “按序追加 + 范围读 + 实时 tail”的事件溯源语义。`TraceStore` 是它的领域包装\n * (`streamId = sessionId`,`E = TraceEvent`)。原设计稿 docs/design/storage-kernel.md 已随\n * RFC-067 M123-03 文档收敛删除,内容并入 packages/persistence/ARCHITECTURE.md,见 commit 5d6a9f5b。\n *\n * 单写者约束:跨进程并发写**不**保证 seq 全序(引擎进程独占写自己的 trace)。\n */\n\nexport interface LogEntry<E> {\n /** 单调递增序号,append 时由后端分配(从 0 起,逐 stream 独立)。等于该 stream 内的行号。 */\n seq: number\n /** 分流键(如 sessionId)。 */\n streamId: string\n /** 领域负载(如 TraceEvent)。 */\n entry: E\n}\n\nexport interface AppendRange {\n /** seq 下界(含),默认 0。 */\n from?: number\n /** seq 上界(不含),默认 +∞。 */\n to?: number\n}\n\nexport interface LogCodec<E> {\n encode(entry: E): string\n decode(payload: string): E\n}\n\nexport interface AppendLog<E> {\n /** 追加一条,返回含已分配 seq 的记录。 */\n append(streamId: string, entry: E): Promise<LogEntry<E>>\n /** 按 seq 升序读取(可选范围);stream 不存在 → 空迭代,不抛。 */\n read(streamId: string, range?: AppendRange): AsyncIterable<LogEntry<E>>\n /**\n * 可选——stream 当前记录数(= 下一个待分配 seq)。宿主用于「只回放最近 N 条」的\n * range 计算(如 inspector backfill 封顶),避免全量 decode 只为拿总数。\n */\n count?(streamId: string): Promise<number>\n /** 先回放已有,再实时跟随后续 append(实时观测底座)。 */\n tail(streamId: string): AsyncIterable<LogEntry<E>>\n /**\n * 截断到 toSeq(保留 seq < toSeq,丢弃 seq ≥ toSeq);下次 append 从 toSeq 续号。\n * 回滚/分叉用(trace 截到 checkpoint 的 traceSeq)。toSeq ≤ 0 等价清空该 stream;\n * toSeq ≥ 当前长度为无操作。stream 不存在 → 无操作,不抛。\n *\n * 注意(单写者语义):已在运行的 tail 迭代器持有自身 lastSeq 游标,truncate 后复用的\n * seq 号不会被同一迭代器重新发出(避免乱序)——回滚分支应重新 tail。\n */\n truncate(streamId: string, toSeq: number): Promise<void>\n /** 删除整个 stream(回滚/分叉用)。 */\n clear(streamId: string): Promise<void>\n /**\n * 可选——释放底层资源(FileAppendLog/HttpAppendLog 实现关闭文件句柄/刷写队列;\n * MemoryAppendLog 无需)。宿主关停时 `await store.close?.()`,取代旧 `'close' in store` 鸭子探测。\n */\n close?(): Promise<void>\n}\n\nexport type AppendLogOptions<E> =\n | { type: 'memory' }\n | {\n type: 'file'\n baseDir: string\n extension?: string\n codec?: LogCodec<E>\n pollIntervalMs?: number\n }\n\n/**\n * RFC-088 D4:追加文件打开标志。O_WRONLY|O_CREAT|O_APPEND + O_DSYNC(每写数据同步落盘)。\n * O_DSYNC 在部分平台未定义时回退为普通追加标志(内容仍写入,仅失去每写 durable 保证)——\n * 优于抛错阻断写入。fallback 平台可经 group-commit 补偿(本 RFC 未实现,留 D4 后续)。\n */\nconst APPEND_FLAGS =\n constants.O_DSYNC != null\n ? constants.O_WRONLY | constants.O_CREAT | constants.O_APPEND | constants.O_DSYNC\n : constants.O_WRONLY | constants.O_CREAT | constants.O_APPEND\n\n/** '\\n' 字节值(0x0A)——字节级行扫描共用。 */\nconst NEWLINE_BYTE = 0x0a\n/** FileHandle 类型别名(fs/promises open 的返回值)。 */\ntype FileHandleLike = Awaited<ReturnType<typeof open>>\n/**\n * RFC-145 D1:常驻写句柄上限。单进程活跃 stream 极少超过(主会话 trace + checkpoint +\n * 少量 fork),超限按 lastUsedAt 驱逐最旧。\n */\nconst MAX_RESIDENT_HANDLES = 8\n/** RFC-145 D1:写句柄 idle 回收阈值——下次 append 时惰性检查(无常驻 timer),\n * 距上次使用超过该时长的其他 stream 句柄关闭,长驻进程静置期不持有 fd。 */\nconst HANDLE_IDLE_MS = 60_000\n/** read/countLines 的分块读缓冲大小。 */\nconst READ_CHUNK_BYTES = 1024 * 1024\n/**\n * 稀疏字节索引间隔(RFC-102 backlog T1):每 INDEX_INTERVAL 行记一个 seq→字节偏移锚点。\n * 240K 行文件只产生 240 个锚点(Map<number,number> 内存开销可忽略),read({from}) 据此\n * seek 到最近锚点而非从文件头逐字节扫——真机验证(260MB/24万行存量 trace,scrubber 高频\n * 精细拖动场景):30 次连续 range read 从 13.3s/单次最大阻塞89ms 降至毫秒级(见提交说明)。\n */\nconst INDEX_INTERVAL = 1000\n/**\n * anchors 数组硬上限(防御性):正常场景(单会话百万行以内)远达不到——1000 万行文件\n * 也只产生 1 万个 ~24B 锚点(~240KB)。上限存在是防异常/畸形场景(循环写入巨量数据)\n * 让索引本身变成无界内存增长;超限后淘汰最早锚点(LRU 语义:scrubber 高频访问的是\n * 最近区域,早期锚点访问价值随时间递减)。\n */\nconst MAX_ANCHORS = 10_000\n\n/**\n * 追加锚点,超过 MAX_ANCHORS 时丢弃最早的一批(保留最近区域——scrubber 高频访问的\n * 是最近区域,早期锚点访问价值随时间递减)。丢弃整数倍 INDEX_INTERVAL 长度的前缀\n * (而非单个),避免每次超限都做一次 O(1) shift 但长期产生 O(N) 次微小 shift 的\n * 摊销退化——批量丢弃 10% 容量,均摊后仍是 O(1)。\n */\nfunction pushAnchorCapped(index: ByteIndex, anchor: { seq: number; byteOffset: number }): void {\n index.anchors.push(anchor)\n if (index.anchors.length > MAX_ANCHORS) {\n index.anchors.splice(0, Math.ceil(MAX_ANCHORS * 0.1))\n }\n}\n\nfunction inRange(seq: number, range?: AppendRange): boolean {\n const from = range?.from ?? 0\n const to = range?.to ?? Number.POSITIVE_INFINITY\n return seq >= from && seq < to\n}\n\ntype TailListener<E> = (record: LogEntry<E>) => void\n\n/**\n * 单 stream 环形上限(RFC-115 §2 遗留 P1 项,2026-07-11 封口):MemoryAppendLog 是\n * sessionDir/remoteLog 均未配置时的兜底后端(`buildEngineStores`,\n * `packages/runtime/src/engine-stores.ts`),此前 `streams` Map 无上限、每条\n * `structuredClone`——配置缺失的长会话/长驻 stream 会无界驻留内存。\n * 该后端本身是\"无持久化\"的降级路径,加环形上限后语义从\"无界内存增长\"\n * 收敛为\"有界降级\"(超限静默丢弃最旧记录,seq 单调计数不受影响)——\n * 与该后端\"尽力而为、非权威存储\"的定位一致。\n */\nconst MEMORY_LOG_MAX_RECORDS_PER_STREAM = 10_000\n\ninterface MemoryStreamState<E> {\n /** 当前保留的记录(可能已驱逐掉最旧的一批,起始 seq ≠ 0)。 */\n records: LogEntry<E>[]\n /** 下一个待分配的 seq——独立于 records.length(驱逐后二者分道)。 */\n nextSeq: number\n /** 已驱逐的记录数(诊断用,随 evict 单调递增)。 */\n droppedCount: number\n}\n\nexport class MemoryAppendLog<E> implements AppendLog<E> {\n private readonly streams = new Map<string, MemoryStreamState<E>>()\n private readonly listeners = new Map<string, Set<TailListener<E>>>()\n private readonly maxRecordsPerStream: number\n private readonly onEvict?: (streamId: string, evictedSeq: number, totalDropped: number) => void\n\n constructor(options?: {\n maxRecordsPerStream?: number\n /** 驱逐发生时的诊断回调(可选,如宿主想告警/记日志)。 */\n onEvict?: (streamId: string, evictedSeq: number, totalDropped: number) => void\n }) {\n this.maxRecordsPerStream = options?.maxRecordsPerStream ?? MEMORY_LOG_MAX_RECORDS_PER_STREAM\n this.onEvict = options?.onEvict\n }\n\n private getOrCreateStream(streamId: string): MemoryStreamState<E> {\n let state = this.streams.get(streamId)\n if (!state) {\n state = { records: [], nextSeq: 0, droppedCount: 0 }\n this.streams.set(streamId, state)\n }\n return state\n }\n\n async append(streamId: string, entry: E): Promise<LogEntry<E>> {\n const state = this.getOrCreateStream(streamId)\n const record: LogEntry<E> = { seq: state.nextSeq, streamId, entry: structuredClone(entry) }\n state.nextSeq++\n state.records.push(record)\n if (state.records.length > this.maxRecordsPerStream) {\n const evicted = state.records.shift()!\n state.droppedCount++\n this.onEvict?.(streamId, evicted.seq, state.droppedCount)\n }\n\n const subscribers = this.listeners.get(streamId)\n if (subscribers) {\n for (const listener of subscribers) {\n listener(structuredClone(record))\n }\n }\n\n return structuredClone(record)\n }\n\n async *read(streamId: string, range?: AppendRange): AsyncIterable<LogEntry<E>> {\n const records = this.streams.get(streamId)?.records ?? []\n for (const record of records) {\n if (inRange(record.seq, range)) {\n yield structuredClone(record)\n }\n }\n }\n\n /** stream 记录数上限(= 下一个 seq,与实际保留条数无关——驱逐后二者分道,保持 seq 单调语义)。 */\n async count(streamId: string): Promise<number> {\n return this.streams.get(streamId)?.nextSeq ?? 0\n }\n\n async *tail(streamId: string): AsyncIterable<LogEntry<E>> {\n const queue: LogEntry<E>[] = []\n let wake: (() => void) | null = null\n const listener: TailListener<E> = (record) => {\n queue.push(record)\n if (wake) {\n wake()\n wake = null\n }\n }\n\n const subscribers = this.listeners.get(streamId) ?? new Set<TailListener<E>>()\n subscribers.add(listener)\n this.listeners.set(streamId, subscribers)\n\n let lastSeq = -1\n try {\n for (const record of this.streams.get(streamId)?.records ?? []) {\n yield structuredClone(record)\n lastSeq = record.seq\n }\n\n while (true) {\n if (queue.length === 0) {\n await new Promise<void>((resolve) => {\n wake = resolve\n })\n }\n for (const record of queue.splice(0, queue.length)) {\n if (record.seq > lastSeq) {\n yield record\n lastSeq = record.seq\n }\n }\n }\n } finally {\n subscribers.delete(listener)\n }\n }\n\n async truncate(streamId: string, toSeq: number): Promise<void> {\n const state = this.streams.get(streamId)\n if (!state) {\n return\n }\n if (toSeq <= 0) {\n this.streams.delete(streamId)\n return\n }\n // 保留 seq < toSeq 的记录(数组下标不再等于 seq——驱逐后 records[0].seq 可能 >0)。\n const cut = state.records.findIndex((r) => r.seq >= toSeq)\n if (cut === -1) {\n return // 全部记录 seq < toSeq,无需截断\n }\n state.records.length = cut\n state.nextSeq = toSeq\n }\n\n async clear(streamId: string): Promise<void> {\n this.streams.delete(streamId)\n }\n}\n\n/**\n * 单个 stream 的稀疏字节索引:`anchors[i] = { seq, byteOffset }`,seq 严格递增、\n * byteOffset 是该 seq 对应行的起始字节位置。`tailSeq`/`tailByteOffset` 记录索引构建\n * 截至的位置(= 下次 append 增量维护的起点;= 冷启动懒构建的进度)。\n */\ninterface ByteIndex {\n anchors: { seq: number; byteOffset: number }[]\n tailSeq: number\n tailByteOffset: number\n}\n\nexport class FileAppendLog<E> implements AppendLog<E> {\n private readonly baseDir: string\n private readonly extension: string\n private readonly codec: LogCodec<E>\n private readonly pollIntervalMs: number\n private readonly seqCounters = new Map<string, number>()\n /** 稀疏字节索引缓存(进程内,非持久化——重启后懒重建)。 */\n private readonly byteIndexes = new Map<string, ByteIndex>()\n /**\n * RFC-145 D1:per-stream 常驻写句柄缓存。追加热路径从每条 open/stat/write/close\n * (~8 syscalls,含 repair 探测)降到 1 次 write——profile 实证长会话进程主线程\n * 非 idle 样本 88% 在 uv_fs 句柄生命周期回调(sample 67571,2026-07-11)。\n * `tailByteOffset` 增量跟踪文件末尾偏移(open 时 stat 一次初始化,每写后累加),\n * 消除 byteIndex 维护的 per-append stat。正确性前提:writeQueue 串行 + O_APPEND +\n * 单写者。失效协议见 invalidateHandle()。\n */\n private readonly handles = new Map<\n string,\n { handle: FileHandleLike; lastUsedAt: number; tailByteOffset: number }\n >()\n /**\n * RFC-145 D2:进程内已执行过尾部修复的 stream 集合。单写者约束下半行只可能来自\n * 上次进程崩溃的残留——进程存活期内自己的写不产生半行(writeFile 整行经 writeQueue\n * 串行原子入队),故 repair 探测每 stream 只需执行一次(冷启动语义:新实例首次\n * append 前仍会 repair)。truncate/clear 后不清除——ftruncate 原子、clear 走\n * unlink+重建,均不产生半行。\n */\n private readonly repairedStreams = new Set<string>()\n private writeQueue: Promise<unknown> = Promise.resolve()\n private dirReady = false\n private closed = false\n\n constructor(options: {\n baseDir: string\n extension?: string\n codec?: LogCodec<E>\n pollIntervalMs?: number\n }) {\n this.baseDir = options.baseDir\n this.extension = options.extension ?? '.jsonl'\n this.pollIntervalMs = options.pollIntervalMs ?? 50\n this.codec = options.codec ?? {\n encode: (entry: E) => JSON.stringify(entry),\n decode: (payload: string) => JSON.parse(payload) as E,\n }\n }\n\n append(streamId: string, entry: E): Promise<LogEntry<E>> {\n // 已关停后拒绝新写入:appendSerial 会 ensureDir() 重建目录,若 dispose/teardown 已删工作区,\n // 迟到的 fire-and-forget trace 写入会重建 trace/ 目录并写文件 → 撞测试 rmdir(ENOENT/ENOTEMPTY)。\n // 返回 noop 记录(非 reject)避免 fire-and-forget sink `void store.append(...)` 产生未处理拒绝。\n if (this.closed) {\n return Promise.resolve({ seq: -1, streamId, entry })\n }\n const task = this.writeQueue.then(() => this.appendSerial(streamId, entry))\n this.writeQueue = task.catch(() => undefined)\n return task\n }\n\n private async appendSerial(streamId: string, entry: E): Promise<LogEntry<E>> {\n await this.ensureDir()\n // RFC-145 D2:repair 探测每 stream 进程内只执行一次(单写者约束下半行只来自\n // 上次进程崩溃残留;进程存活期内自己的写不产生半行)。\n if (!this.repairedStreams.has(streamId)) {\n await this.repairPartialTail(streamId)\n this.repairedStreams.add(streamId)\n }\n const seq = await this.nextSeq(streamId)\n const record: LogEntry<E> = { seq, streamId, entry }\n const line = `${this.codec.encode(entry)}\\n`\n const lineBytes = Buffer.byteLength(line, 'utf-8')\n\n // RFC-088 D4 + RFC-145 D1:O_DSYNC 常驻句柄——O_DSYNC 是 open file description 属性,\n // 句柄复用不影响每写数据同步落盘语义(durable 保证不变);热路径从每条\n // open/stat/write/close 降到 1 次 write。写失败(EBADF 等外部失效)降级为\n // 关缓存句柄 + 重新 open 重试一次。\n let cached = this.handles.get(streamId)\n try {\n if (!cached) {\n cached = await this.openResidentHandle(streamId)\n }\n try {\n await cached.handle.writeFile(line)\n } catch {\n // 缓存句柄失效(文件被外部删除/替换等)→ 重开重试一次\n await this.invalidateHandle(streamId)\n cached = await this.openResidentHandle(streamId)\n await cached.handle.writeFile(line)\n }\n } catch (error) {\n await this.invalidateHandle(streamId).catch(() => undefined)\n throw new PersistenceError(`Failed to append to stream \"${streamId}\"`, { cause: error })\n }\n\n // 索引维护零额外 syscall:写入起点 = 句柄缓存的 tailByteOffset(open 时 stat 初始化,\n // 每写后累加;writeQueue 串行 + O_APPEND + 单写者保证其与真实文件末尾一致)。\n const index = this.byteIndexes.get(streamId)\n if (index && index.tailSeq === seq) {\n this.recordIndexPoint(streamId, seq, cached.tailByteOffset, lineBytes)\n }\n cached.tailByteOffset += lineBytes\n cached.lastUsedAt = Date.now()\n this.seqCounters.set(streamId, seq + 1)\n return record\n }\n\n /**\n * RFC-145 D1:打开并缓存常驻写句柄。open 时 stat 一次初始化 tailByteOffset;\n * 入缓存前执行上限驱逐(LRU)与 idle 回收(惰性,无常驻 timer)。\n * 仅在 writeQueue 串行域内调用。\n */\n private async openResidentHandle(\n streamId: string,\n ): Promise<{ handle: FileHandleLike; lastUsedAt: number; tailByteOffset: number }> {\n const now = Date.now()\n // idle 回收:静置超阈值的其他 stream 句柄关闭,长驻进程夜间不持有 fd\n for (const [id, entry] of this.handles) {\n if (id !== streamId && now - entry.lastUsedAt > HANDLE_IDLE_MS) {\n this.handles.delete(id)\n await entry.handle.close().catch(() => undefined)\n }\n }\n // 上限驱逐:按 lastUsedAt 驱逐最旧\n while (this.handles.size >= MAX_RESIDENT_HANDLES) {\n let oldestId: string | undefined\n let oldestAt = Infinity\n for (const [id, entry] of this.handles) {\n if (entry.lastUsedAt < oldestAt) {\n oldestAt = entry.lastUsedAt\n oldestId = id\n }\n }\n if (oldestId === undefined) break\n const evicted = this.handles.get(oldestId)!\n this.handles.delete(oldestId)\n await evicted.handle.close().catch(() => undefined)\n }\n const handle = await open(this.resolvePath(streamId), APPEND_FLAGS)\n const { size } = await handle.stat()\n const entry = { handle, lastUsedAt: now, tailByteOffset: size }\n this.handles.set(streamId, entry)\n return entry\n }\n\n /**\n * RFC-145 D1 失效协议:关闭并移除某 stream 的常驻写句柄(连同 tailByteOffset)。\n * 四条路径在操作 stream 文件前必须调用:truncateSerial / clearSerial /\n * repairPartialTail / close()——它们各自 open 独立句柄操作文件,缓存句柄若还活着,\n * 后续 append 会写到被 truncate/unlink 的旧 inode(数据静默丢失)。\n * 均在 writeQueue 串行域内执行,无并发窗口。\n */\n private async invalidateHandle(streamId: string): Promise<void> {\n const cached = this.handles.get(streamId)\n if (!cached) return\n this.handles.delete(streamId)\n await cached.handle.close().catch(() => undefined)\n }\n\n /**\n * 索引增量维护:每 INDEX_INTERVAL 行落一个锚点,并推进 tailSeq/tailByteOffset\n * (下次 append 据此判断能否零 IO 继续维护)。只在索引已就位(冷启动懒构建过一次)\n * 时生效——否则每次 append 都要额外 stat 判断是否该建索引,得不偿失。\n *\n * 单调性守卫(`seq < index.tailSeq` 提前返回):append 与 read() 都会推进同一 stream\n * 的索引 tailSeq,二者不互斥(read 不在 writeQueue 内)。appendSerial 在\n * `stat()` 到这里之间有 await 缺口,若同一窗口内一次并发 read() 已把 tailSeq 推进到\n * 更前面,此处必须放弃写入,否则会用旧的 seq/byteOffset 把 tailSeq 拉回退——\n * 破坏 nearestAnchor 依赖的 anchors 严格递增假设。\n */\n private recordIndexPoint(streamId: string, seq: number, byteOffset: number, lineLen: number): void {\n const index = this.byteIndexes.get(streamId)\n if (!index || seq < index.tailSeq) return\n if (seq % INDEX_INTERVAL === 0) {\n pushAnchorCapped(index, { seq, byteOffset })\n }\n index.tailSeq = seq + 1\n index.tailByteOffset = byteOffset + lineLen\n }\n\n /**\n * 冷启动/索引缺口时的懒构建:顺序扫描一遍文件,边扫边记锚点(与 read() 的分块扫描\n * 复用同一遍 IO,零额外开销——调用方(read)在扫描主循环里同步喂入行边界)。\n * 返回构建后的索引(供 read() 立即复用做本次的 seek 判断)。\n *\n * TOCTOU 说明:并发 read() 首次访问同一 streamId 时,Map.get/set 之间无 await 缺口\n * (本方法全同步),不存在两次并发调用互相覆盖对方索引对象的竞态——JS 单线程下\n * 同步代码段天然原子。\n */\n private ensureByteIndex(streamId: string): ByteIndex {\n let index = this.byteIndexes.get(streamId)\n if (!index) {\n index = { anchors: [], tailSeq: 0, tailByteOffset: 0 }\n this.byteIndexes.set(streamId, index)\n }\n return index\n }\n\n /** 索引中 ≤ from 的最近锚点(无锚点或 from=0 时退化为文件头,即旧行为)。 */\n private nearestAnchor(index: ByteIndex, from: number): { seq: number; byteOffset: number } {\n let best = { seq: 0, byteOffset: 0 }\n for (const a of index.anchors) {\n if (a.seq <= from && a.seq >= best.seq) {\n best = a\n } else if (a.seq > from) {\n break\n }\n }\n return best\n }\n\n /**\n * 尾部半行修复(崩溃恢复):只检查/裁剪文件尾部,O(尾部) 而非 O(文件)。\n * 旧实现全量 readFile + atomicWriteFile 全量重写——巨型 trace(数百 MB)下不可用。\n * 现:stat + 读最后 1 字节判完整;不完整时从尾部按块回扫找最后一个 '\\n',`ftruncate`\n * 原地截断(truncate 掉的只是半行垃圾,无崩溃安全顾虑——重复截断幂等)。\n */\n private async repairPartialTail(streamId: string): Promise<void> {\n // RFC-145 D1 失效协议:repair 用独立 'r+' 句柄且可能 ftruncate——先失效常驻写句柄\n await this.invalidateHandle(streamId)\n const path = this.resolvePath(streamId)\n let handle: Awaited<ReturnType<typeof open>>\n try {\n handle = await open(path, 'r+')\n } catch (error) {\n if (isEnoent(error)) {\n return\n }\n throw new PersistenceError(`Failed to read stream \"${streamId}\"`, { cause: error })\n }\n try {\n const { size } = await handle.stat()\n if (size === 0) {\n return\n }\n const last = Buffer.alloc(1)\n await handle.read(last, 0, 1, size - 1)\n if (last[0] === NEWLINE_BYTE) {\n return\n }\n const CHUNK = 64 * 1024\n let cut = -1\n let scanEnd = size\n while (scanEnd > 0 && cut === -1) {\n const start = Math.max(0, scanEnd - CHUNK)\n const buf = Buffer.alloc(scanEnd - start)\n await handle.read(buf, 0, buf.length, start)\n for (let i = buf.length - 1; i >= 0; i--) {\n if (buf[i] === NEWLINE_BYTE) {\n cut = start + i + 1\n break\n }\n }\n scanEnd = start\n }\n await handle.truncate(cut === -1 ? 0 : cut)\n } catch (error) {\n throw new PersistenceError(`Failed to repair stream \"${streamId}\"`, { cause: error })\n } finally {\n await handle.close()\n }\n }\n\n /**\n * 流式逐行读取:分块读 + 增量 decode,只对范围内的行做 JSON decode。\n * 旧实现 loadAll 全量 readFile + 全行 JSON.parse——260MB trace 下 ~1s 主线程阻塞\n * 且峰值内存 >1.4GB;带 range 时(如 inspector backfill 只要最近 N 条)纯浪费。\n *\n * RFC-102 backlog T1:`range.from` 命中已建索引区间时,直接 seek 到最近锚点字节偏移\n * (`handle.read(buf, 0, len, position)` 的 position 参数跳过前缀 IO,不只是跳过 decode)\n * ——替代旧实现「总是从字节 0 顺序扫到 from」。真机验证(260MB/24万行存量 trace,\n * scrubber 高频精细拖动场景):30 次连续 range read 从 13.3s/单次最大阻塞89ms 降至毫秒级。\n * 索引未覆盖 from(冷启动/gap)时透明退化为旧的顺序扫描,同时顺路把扫过的区间记入索引——\n * 首次访问某区域仍是 O(距离),但此后同区域内的精细拖动全部命中索引。\n */\n async *read(streamId: string, range?: AppendRange): AsyncIterable<LogEntry<E>> {\n const from = range?.from ?? 0\n const to = range?.to ?? Number.POSITIVE_INFINITY\n if (to <= from) {\n return\n }\n let handle: Awaited<ReturnType<typeof open>>\n try {\n handle = await open(this.resolvePath(streamId), 'r')\n } catch (error) {\n if (isEnoent(error)) {\n return\n }\n throw new PersistenceError(`Failed to read stream \"${streamId}\"`, { cause: error })\n }\n try {\n // 索引命中:seek 到 ≤from 的最近锚点字节偏移,跳过前缀 IO(不只是跳过 decode)。\n // from 落在已探索区(<tailSeq)用最近锚点;from 落在未探索区(≥tailSeq,如索引刚\n // 冷启动或 from 超出已扫过的范围)不退化到文件头重扫——从 tailSeq/tailByteOffset\n // (已探索边界)继续往后扫,天然比从 0 扫更接近目标(tailSeq 本身也是一种锚点)。\n // 顺路把本次扫过的区间记入索引——首次访问某区域仍是 O(距离),此后精细拖动命中索引。\n const index = this.ensureByteIndex(streamId)\n const anchor =\n from < index.tailSeq\n ? this.nearestAnchor(index, from)\n : { seq: index.tailSeq, byteOffset: index.tailByteOffset }\n\n const buf = Buffer.alloc(READ_CHUNK_BYTES)\n const decoder = new StringDecoder('utf-8')\n let pending = ''\n let seq = anchor.seq\n // 当前未解码行的起始字节偏移——只在「找到一个 '\\n'」时推进,不随分块读取重置,\n // 保证跨块行(pending 携带半行数据到下一块)时偏移仍然正确。\n let lineStartByte = anchor.byteOffset\n let readPos = anchor.byteOffset\n while (true) {\n const chunkBase = readPos\n const { bytesRead } = await handle.read(buf, 0, READ_CHUNK_BYTES, readPos)\n if (bytesRead === 0) {\n break\n }\n readPos += bytesRead\n\n let chunkOffset = 0\n // 锚点到 from 之间的缺口(seq<from 且尚未越过已建索引边界):纯字节数 '\\n',\n // 零 decode/slice——profile 实证 260MB 前缀的 string_decoder.write 占\n // backfill ~43% 耗时,索引只消除了「从 0 开始」的那段距离,这段缺口仍需跳过。\n while (seq < from && seq < index.tailSeq && chunkOffset < bytesRead) {\n const nlAt = buf.subarray(chunkOffset, bytesRead).indexOf(NEWLINE_BYTE)\n if (nlAt === -1) {\n chunkOffset = bytesRead\n break\n }\n chunkOffset += nlAt + 1\n seq++\n lineStartByte = chunkBase + chunkOffset\n }\n if (chunkOffset >= bytesRead) {\n continue\n }\n\n pending += decoder.write(buf.subarray(chunkOffset, bytesRead))\n let nl: number\n while ((nl = pending.indexOf('\\n')) >= 0) {\n const line = pending.slice(0, nl)\n const lineEndByte = lineStartByte + Buffer.byteLength(line, 'utf-8') + 1\n pending = pending.slice(nl + 1)\n if (line.length === 0) {\n lineStartByte = lineEndByte\n continue\n }\n // 只在「本次扫描越过已知边界」时才写索引——避免同一 seq 因多次 range read\n // 重复 push 锚点(幂等:tailSeq 严格递增,seq < tailSeq 的行已记录过)。\n if (seq >= index.tailSeq) {\n if (seq % INDEX_INTERVAL === 0) {\n pushAnchorCapped(index, { seq, byteOffset: lineStartByte })\n }\n index.tailSeq = seq + 1\n index.tailByteOffset = lineEndByte\n }\n if (seq >= from) {\n yield { seq, streamId, entry: this.codec.decode(line) }\n }\n seq++\n lineStartByte = lineEndByte\n if (seq >= to) {\n return\n }\n }\n }\n } finally {\n await handle.close()\n }\n }\n\n /** stream 记录数(= 下一个 seq)。优先用进程内 seq 缓存;否则字节流数行(不 decode)。 */\n async count(streamId: string): Promise<number> {\n const cached = this.seqCounters.get(streamId)\n if (cached !== undefined) {\n return cached\n }\n const total = await this.countLines(streamId)\n this.seqCounters.set(streamId, total)\n return total\n }\n\n /**\n * 增量轮询:只读上次字节偏移后新追加的部分、只 decode 新行。\n * 旧实现每 poll `loadAll` 重读整个文件 + 全行 JSON.parse(O(N)/poll)——长会话下吃满 CPU/GC、\n * 阻塞事件循环致输入卡顿(profile 实证 decode/loadAll/string_decoder 占 ~88% CPU)。现降到 O(新增)。\n */\n async *tail(streamId: string): AsyncIterable<LogEntry<E>> {\n const path = this.resolvePath(streamId)\n let emitted = 0\n let bytePos = 0\n let pending = ''\n\n while (!this.closed) {\n let handle: Awaited<ReturnType<typeof open>> | undefined\n try {\n handle = await open(path, 'r')\n } catch (error) {\n if (isEnoent(error)) {\n await delay(this.pollIntervalMs)\n continue\n }\n throw new PersistenceError(`Failed to read stream \"${streamId}\"`, { cause: error })\n }\n try {\n const { size } = await handle.stat()\n if (size < bytePos) {\n // 文件被 truncate/clear 缩短 → 从头重读\n bytePos = 0\n emitted = 0\n pending = ''\n }\n if (size > bytePos) {\n const len = size - bytePos\n const buf = Buffer.alloc(len)\n await handle.read(buf, 0, len, bytePos)\n bytePos = size\n pending += buf.toString('utf-8')\n let nl: number\n while ((nl = pending.indexOf('\\n')) >= 0) {\n const line = pending.slice(0, nl)\n pending = pending.slice(nl + 1)\n if (line.length === 0) continue\n yield { seq: emitted, streamId, entry: this.codec.decode(line) }\n emitted++\n }\n }\n } finally {\n await handle.close()\n }\n if (this.closed) break\n await delay(this.pollIntervalMs)\n }\n }\n\n /**\n * 停止所有进行中的 tail() 轮询循环——while(!closed) 在下个轮询点退出,常驻 tail 订阅随之终止。\n * app.stop() 经可选链 store.close?.() 调用本方法(fix: /exit·Ctrl+C 退不掉):缺此方法时,\n * inspector 的常驻 trace tail 永久轮询,是 /exit、Ctrl+C 退不掉的近因。\n */\n async close(): Promise<void> {\n this.closed = true\n // 排空在途写入队列:close 返回前确保所有已入队的 append/truncate 落盘完成。app.stop() 经\n // `await traceStore.close?.()` 调用本方法,故 stop 返回后无残留写 → 宿主/测试可安全删工作区。\n // (此前只置 closed、不排空 → fire-and-forget trace 写入在 stop 后仍跑,撞 rmdir 竞态。)\n await this.writeQueue.catch(() => undefined)\n // RFC-145 D1 失效协议(4):排空后关闭全部常驻写句柄\n for (const [, entry] of this.handles) {\n await entry.handle.close().catch(() => undefined)\n }\n this.handles.clear()\n }\n\n truncate(streamId: string, toSeq: number): Promise<void> {\n if (toSeq <= 0) {\n return this.clear(streamId)\n }\n\n const task = this.writeQueue.then(() => this.truncateSerial(streamId, toSeq))\n this.writeQueue = task.catch(() => undefined)\n return task\n }\n\n private async truncateSerial(streamId: string, toSeq: number): Promise<void> {\n // RFC-145 D1 失效协议:ftruncate 使常驻写句柄的 tailByteOffset 失效——先关闭移除\n await this.invalidateHandle(streamId)\n // 分块字节扫描找第 toSeq 个 '\\n' 的字节偏移,`ftruncate` 原地截断——替代旧\n // 全量 readFile + atomicWriteFile 重写(巨型 trace 下内存/IO 双倍开销)。\n // ftruncate 本身原子(成功或不变),无半写窗口。\n let handle: Awaited<ReturnType<typeof open>>\n try {\n handle = await open(this.resolvePath(streamId), 'r+')\n } catch (error) {\n if (isEnoent(error)) {\n return\n }\n throw new PersistenceError(`Failed to read stream \"${streamId}\"`, { cause: error })\n }\n try {\n const buf = Buffer.alloc(READ_CHUNK_BYTES)\n let lineCount = 0\n let bytePos = 0\n let cutIndex = -1\n outer: while (true) {\n const { bytesRead } = await handle.read(buf, 0, READ_CHUNK_BYTES, null)\n if (bytesRead === 0) {\n break\n }\n for (let i = 0; i < bytesRead; i++) {\n if (buf[i] === NEWLINE_BYTE) {\n lineCount++\n if (lineCount === toSeq) {\n cutIndex = bytePos + i + 1\n break outer\n }\n }\n }\n bytePos += bytesRead\n }\n\n if (cutIndex === -1) {\n return\n }\n await handle.truncate(cutIndex)\n } catch (error) {\n throw new PersistenceError(`Failed to truncate stream \"${streamId}\"`, { cause: error })\n } finally {\n await handle.close()\n }\n this.seqCounters.set(streamId, toSeq)\n // 截断使文件尾部字节偏移全部失效——丢弃索引而非修剪(修剪需重新扫描哪些锚点仍\n // <toSeq,收益不值当;truncate 是低频操作,下次 read 冷启动重建即可)。\n this.byteIndexes.delete(streamId)\n }\n\n clear(streamId: string): Promise<void> {\n const task = this.writeQueue.then(() => this.clearSerial(streamId))\n this.writeQueue = task.catch(() => undefined)\n return task\n }\n\n private async clearSerial(streamId: string): Promise<void> {\n // RFC-145 D1 失效协议:unlink 后缓存句柄指向旧 inode——先关闭移除\n await this.invalidateHandle(streamId)\n this.seqCounters.delete(streamId)\n this.byteIndexes.delete(streamId)\n try {\n await unlink(this.resolvePath(streamId))\n } catch (error) {\n if (!isEnoent(error)) {\n throw new PersistenceError(`Failed to clear stream \"${streamId}\"`, { cause: error })\n }\n }\n }\n\n /** 字节流数行(数 '\\n',不 decode JSON)——nextSeq/count 冷启动用,O(文件) IO 但零 parse/GC。 */\n private async countLines(streamId: string): Promise<number> {\n let handle: Awaited<ReturnType<typeof open>>\n try {\n handle = await open(this.resolvePath(streamId), 'r')\n } catch (error) {\n if (isEnoent(error)) {\n return 0\n }\n throw new PersistenceError(`Failed to read stream \"${streamId}\"`, { cause: error })\n }\n try {\n const buf = Buffer.alloc(READ_CHUNK_BYTES)\n let count = 0\n while (true) {\n const { bytesRead } = await handle.read(buf, 0, READ_CHUNK_BYTES, null)\n if (bytesRead === 0) {\n break\n }\n for (let i = 0; i < bytesRead; i++) {\n if (buf[i] === NEWLINE_BYTE) {\n count++\n }\n }\n }\n return count\n } finally {\n await handle.close()\n }\n }\n\n private async nextSeq(streamId: string): Promise<number> {\n const cached = this.seqCounters.get(streamId)\n if (cached !== undefined) {\n return cached\n }\n const next = await this.countLines(streamId)\n this.seqCounters.set(streamId, next)\n return next\n }\n\n private resolvePath(streamId: string): string {\n return join(this.baseDir, `${encodeURIComponent(streamId)}${this.extension}`)\n }\n\n private async ensureDir(): Promise<void> {\n if (this.dirReady) {\n return\n }\n await mkdir(this.baseDir, { recursive: true })\n this.dirReady = true\n }\n\n}\n\n/**\n * 统一日志工厂:按 `options.type` 直构造内置后端(memory/file)。\n * `AppendLogOptions` 穷尽判别联合,switch 按 type 窄化——去 `as unknown as`。\n */\nexport function createAppendLog<E>(options: AppendLogOptions<E>): AppendLog<E> {\n switch (options.type) {\n case 'memory':\n return new MemoryAppendLog() as AppendLog<E>\n case 'file':\n return new FileAppendLog({\n baseDir: options.baseDir,\n extension: options.extension,\n codec: options.codec,\n pollIntervalMs: options.pollIntervalMs,\n }) as AppendLog<E>\n default:\n throw new Error(`Unsupported append-log type: ${(options as { type: string }).type}`)\n }\n}\n\n/** 内存实时旁路:订阅某 stream 的后续 append(live 观测者用,避免绕磁盘 tail 轮询)。 */\nexport interface LiveTap<E> {\n /** 订阅 streamId 的后续 append;返回取消订阅函数。无订阅者时 publish 为 no-op。 */\n subscribe(streamId: string, fn: (record: LogEntry<E>) => void): () => void\n}\n\nclass LiveAppendBus<E> implements LiveTap<E> {\n private readonly subs = new Map<string, Set<(record: LogEntry<E>) => void>>()\n publish(record: LogEntry<E>): void {\n const set = this.subs.get(record.streamId)\n if (!set) return\n for (const fn of [...set]) fn(record)\n }\n subscribe(streamId: string, fn: (record: LogEntry<E>) => void): () => void {\n let set = this.subs.get(streamId)\n if (!set) this.subs.set(streamId, (set = new Set()))\n set.add(fn)\n return () => {\n const s = this.subs.get(streamId)\n if (!s) return\n s.delete(fn)\n if (s.size === 0) this.subs.delete(streamId)\n }\n }\n}\n\n/**\n * 给 AppendLog 套一层内存实时旁路(L3 终局):`append` 在写底层 store 的同时把记录 publish 给内存订阅者。\n * live 观测者(inspector)订阅 `live` 拿实时 append,**不再 `tail` 磁盘轮询**——live 进程内数据不绕磁盘;\n * 底层 store 仍负责持久化与「回放过去会话」的 `read`/`tail`。\n */\nexport function createLiveTappedAppendLog<E>(inner: AppendLog<E>): {\n store: AppendLog<E>\n live: LiveTap<E>\n} {\n const bus = new LiveAppendBus<E>()\n const store: AppendLog<E> = {\n async append(streamId, entry) {\n const record = await inner.append(streamId, entry)\n bus.publish(record)\n return record\n },\n read: (streamId, range) => inner.read(streamId, range),\n tail: (streamId) => inner.tail(streamId),\n truncate: (streamId, toSeq) => inner.truncate(streamId, toSeq),\n clear: (streamId) => inner.clear(streamId),\n ...(inner.count ? { count: (streamId: string) => inner.count!(streamId) } : {}),\n ...(inner.close ? { close: () => inner.close!() } : {}),\n }\n return { store, live: bus }\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => {\n // unref:tail() 轮询用此 delay。不 unref 会让一个常驻 tail(如 inspector 的 trace 订阅)\n // 每 pollIntervalMs 重新 arm 一个 ref 定时器,永久把 Node 事件循环吊住 → /exit、Ctrl+C 后\n // 进程不退(fix: /exit·Ctrl+C 退不掉)。unref 后 tail 仍正常轮询,但不再单独阻止进程退出。\n const timer = setTimeout(resolve, ms)\n timer.unref?.()\n })\n}\n\nfunction isEnoent(error: unknown): boolean {\n return (\n typeof error === 'object' &&\n error !== null &&\n 'code' in error &&\n (error as { code: string }).code === 'ENOENT'\n )\n}\n","import { join } from 'node:path'\nimport { FilePersistence } from './file-persistence'\nimport { saveDocument } from './save-document'\nimport { MemoryAppendLog } from './append-log'\n\nimport type { PaginatedResult, Persistence, Snapshot } from './types'\nimport type { ChangeLogEntry } from './change-log'\nimport type { AppendLog, LogEntry } from './append-log'\n\n/**\n * 可选能力:基于变更日志的增量查询(仅 SqlitePersistence 实现;file/memory/http 后端无此能力)。\n * RFC-074 R-LAYER:以**显式能力接口 + 类型守卫**取代原 `persistence as … & Partial<…>` 鸭子 cast,\n * 让\"该后端是否支持变更追踪\"在类型与运行时都显式可见,而非静默 no-op。\n */\nexport interface ChangeTrackingPersistence {\n getChangeLog(ns: string, since: number, limit?: number): ChangeLogEntry[]\n getChangesSince(ns: string, since: number, ids?: string[]): { ids: string[]; cursor: number }\n getDeletedSince(ns: string, since: number): string[]\n}\n\n/** 类型守卫:后端是否实现变更追踪三方法(全有才算支持,否则走全量扫描兜底)。 */\nexport function supportsChangeTracking(\n persistence: Persistence<unknown>,\n): persistence is Persistence<unknown> & ChangeTrackingPersistence {\n const c = persistence as Partial<ChangeTrackingPersistence>\n return (\n typeof c.getChangeLog === 'function' &&\n typeof c.getChangesSince === 'function' &&\n typeof c.getDeletedSince === 'function'\n )\n}\n\nexport interface StorageHostOptions {\n /** 允许的 namespace 白名单(如 ['sessions'];knowledge/archive 已退场)。 */\n namespaces: string[]\n /** 每 namespace 的后端工厂。 */\n backend: (namespace: string) => Persistence<unknown>\n /** 时间源(信封 createdAt/updatedAt),缺省真实时钟。 */\n now?: () => number\n}\n\nconst DEFAULT_NAMESPACES = ['sessions']\n\nexport class StorageHost {\n private readonly namespaces: ReadonlySet<string>\n private readonly backend: (namespace: string) => Persistence<unknown>\n private readonly now: () => number\n private readonly instances = new Map<string, Persistence<unknown>>()\n /** 每 namespace 的 append-only 日志(惰性创建)。 */\n private readonly appendLogs = new Map<string, AppendLog<unknown>>()\n\n constructor(options: StorageHostOptions) {\n this.namespaces = new Set(options.namespaces)\n this.backend = options.backend\n this.now = options.now ?? (() => Date.now())\n }\n\n has(namespace: string): boolean {\n return this.namespaces.has(namespace)\n }\n\n /** 返回所有注册的 namespace 列表(health 路由使用)。 */\n listNamespaces(): string[] {\n return [...this.namespaces]\n }\n\n /**\n * workspace 分区——wsKey 前缀到文档 ID,在 namespace 内实现物理分区。\n * wsKey = `ws_<id>`,缺省 `default`(向后兼容旧调用方)。\n */\n private wsId(wsKey: string | undefined, id: string): string {\n const wk = wsKey?.trim() || 'default'\n return `${wk}/${id}`\n }\n\n /**\n * 追加一条 entry 到指定 stream 的 append-only 日志。\n *\n * 返回含已分配 seq 的 LogEntry——seq 由 AppendLog 保证单写者递增(0-based per stream)。\n * 默认使用 MemoryAppendLog(进程内原子追加);调用侧(service route)负责\n * 在 append 后通过 EventBus 广播 SSE 实时通知。\n *\n * @param namespace 命名空间(如 'sessions')\n * @param wsKey workspace 分区键(ws_<id>),前缀文档 ID 在 namespace 内物理分区\n * @param streamId 流 ID(通常为文档/会话 ID)\n * @param entry 要追加的条目数据\n * @returns 含 seq、streamId、entry 的 LogEntry\n */\n async appendEntry(\n namespace: string,\n wsKey: string | undefined,\n streamId: string,\n entry: unknown,\n ): Promise<LogEntry<unknown>> {\n if (!this.namespaces.has(namespace)) {\n throw new Error(`Unknown storage namespace: ${namespace}`)\n }\n\n let log = this.appendLogs.get(namespace)\n if (!log) {\n log = new MemoryAppendLog<unknown>()\n this.appendLogs.set(namespace, log)\n }\n\n return log.append(this.wsId(wsKey, streamId), entry)\n }\n\n async save(\n namespace: string,\n wsKey: string | undefined,\n id: string,\n document: unknown,\n agentId?: string,\n ): Promise<void> {\n const persistence = this.resolve(namespace)\n await saveDocument(persistence, this.wsId(wsKey, id), document, {\n now: this.now(),\n owner: agentId,\n updatedBy: agentId,\n })\n }\n\n async updateMetadata(\n namespace: string,\n wsKey: string | undefined,\n id: string,\n patch: Record<string, unknown>,\n agentId?: string,\n ): Promise<void> {\n const persistence = this.resolve(namespace)\n const snapshot = await persistence.load(this.wsId(wsKey, id))\n if (!snapshot) throw new Error(`Document \"${id}\" not found in \"${namespace}\"`)\n\n snapshot.metadata = { ...snapshot.metadata, ...patch }\n if (agentId) snapshot.metadata.updatedBy = agentId\n\n await persistence.save(snapshot)\n }\n\n async load(namespace: string, wsKey: string | undefined, id: string): Promise<unknown | null> {\n const snapshot = await this.resolve(namespace).load(this.wsId(wsKey, id))\n return snapshot ? snapshot.data : null\n }\n\n async delete(namespace: string, wsKey: string | undefined, id: string): Promise<boolean> {\n return this.resolve(namespace).delete(this.wsId(wsKey, id))\n }\n\n async list(\n namespace: string,\n wsKey?: string,\n agentId?: string,\n scope?: 'owned' | 'visible' | 'all',\n ): Promise<string[]> {\n const allIds = await this.resolve(namespace).list()\n\n const prefix = `${wsKey || 'default'}/`\n const stripped: { clean: string; prefixed: string }[] = []\n for (const id of allIds) {\n if (id.startsWith(prefix)) stripped.push({ clean: id.slice(prefix.length), prefixed: id })\n }\n const ids = stripped.map((s) => s.clean)\n if (!agentId || !scope || scope === 'all') return ids\n\n const filtered: string[] = []\n for (const { clean, prefixed } of stripped) {\n const snapshot = await this.resolve(namespace).load(prefixed)\n if (!snapshot) continue\n const meta = snapshot.metadata\n if (scope === 'owned' && meta.owner === agentId) {\n filtered.push(clean)\n } else if (\n scope === 'visible' &&\n (meta.owner === agentId || meta.visibility === 'shared' || !meta.visibility)\n ) {\n filtered.push(clean)\n }\n }\n return filtered\n }\n\n /**\n * 增量拉取(sync pull 数据源)。\n *\n * 返回自 `since` (unix ms) 以来 updated_at > since 的文档快照列表,\n * 以及最新游标 cursor(所有文档中最大的 updated_at,供下次 pull 使用)。\n *\n * SQLite 后端走索引查询(O(log n));file/memory 后端全量加载后内存过滤。\n */\n async getChangesSince(\n namespace: string,\n wsKey: string | undefined,\n since: number,\n ids?: string[],\n ): Promise<{ documents: Map<string, unknown | null>; cursor: number }> {\n const persistence = this.resolve(namespace)\n const prefix = wsKey ? `${wsKey}/` : 'default/'\n\n if (supportsChangeTracking(persistence)) {\n const prefixedIds = ids?.map((id) => `${prefix}${id}`)\n const result = persistence.getChangesSince(namespace, since, prefixedIds)\n\n const documents = new Map<string, unknown | null>()\n for (const id of result.ids) {\n if (!id.startsWith(prefix)) continue\n const cleanId = id.slice(prefix.length)\n const snapshot = await persistence.load(id)\n documents.set(cleanId, snapshot?.data ?? null)\n }\n\n const deleted = persistence.getDeletedSince(namespace, since)\n for (const id of deleted) {\n if (!id.startsWith(prefix)) continue\n const cleanId = id.slice(prefix.length)\n if (!documents.has(cleanId)) {\n documents.set(cleanId, null)\n }\n }\n\n return { documents, cursor: result.cursor }\n }\n\n const allIds = await persistence.list()\n const documents = new Map<string, unknown | null>()\n let cursor = since\n\n for (const id of allIds) {\n if (!id.startsWith(prefix)) continue\n const cleanId = id.slice(prefix.length)\n if (ids && !ids.includes(cleanId)) continue\n const snapshot = await persistence.load(id)\n if (!snapshot) continue\n\n if (snapshot.metadata.updatedAt > since) {\n documents.set(cleanId, snapshot.data)\n cursor = Math.max(cursor, snapshot.metadata.updatedAt)\n }\n }\n\n return { documents, cursor }\n }\n\n async listPaginated(\n namespace: string,\n wsKey: string | undefined,\n options: { page: number; pageSize: number; sortBy: string; order: 'asc' | 'desc' },\n ): Promise<PaginatedResult<string>> {\n const persistence = this.resolve(namespace)\n const allIds = await persistence.list()\n\n const prefix = `${wsKey || 'default'}/`\n const idMap = new Map<string, string>()\n for (const id of allIds) {\n if (id.startsWith(prefix)) idMap.set(id, id.slice(prefix.length))\n }\n\n const snapshots: Snapshot<unknown>[] = []\n for (const [prefixed, clean] of idMap) {\n const snapshot = await persistence.load(prefixed)\n if (snapshot) {\n snapshots.push({ ...snapshot, id: clean })\n }\n }\n\n const direction = options.order === 'asc' ? 1 : -1\n snapshots.sort(\n (a, b) => direction * (this.sortValue(a, options.sortBy) - this.sortValue(b, options.sortBy)),\n )\n\n const total = snapshots.length\n const totalPages = Math.max(1, Math.ceil(total / options.pageSize))\n // F12(post-RFC-092 review):0-based page,与 Persistence 全部后端实现\n // (disk/memory/sqlite/remote)及 SessionStore 保持一致——此前 1-based 与\n // RemotePersistence 客户端(0-based)经 HTTP 跨进程直连,是真实的线上协议 bug:\n // 客户端请求 page=1(0-based\"第二页\")在服务端按 1-based 解析出 offset=0,\n // 永远原路返回第一页,第二页及以后不可达。\n const safePage = Math.max(0, Math.min(options.page, totalPages - 1))\n const start = safePage * options.pageSize\n\n return {\n items: snapshots.slice(start, start + options.pageSize).map((snapshot) => snapshot.id),\n total,\n page: safePage,\n pageSize: options.pageSize,\n totalPages,\n hasNext: safePage < totalPages - 1,\n hasPrevious: safePage > 0,\n }\n }\n\n /** 信封 metadata[sortBy] → 文档 metadata?.[sortBy] → 信封 updatedAt 兜底。 */\n private sortValue(snapshot: Snapshot<unknown>, sortBy: string): number {\n const envelope = snapshot.metadata[sortBy]\n if (typeof envelope === 'number') {\n return envelope\n }\n\n const documentMetadata = (snapshot.data as { metadata?: Record<string, unknown> } | null)\n ?.metadata\n const domain = documentMetadata?.[sortBy]\n if (typeof domain === 'number') {\n return domain\n }\n\n return snapshot.metadata.updatedAt\n }\n\n /**\n * 实时事件流(SSE 推送来源)。\n *\n * 轮询后端 change_log 并 yield 变更事件——仅 SQLite 后端支持;file/memory 后端返回空迭代。\n * 设计决策:用鸭子类型(`getChangeLog` 方法探测)而非直接 import SqlitePersistence,\n * 保持 StorageHost 对具体后端的零耦合。\n *\n * @param signal 取消信号——客户端断连时中止轮询,避免生成器泄漏。\n */\n async *streamEvents(\n namespace: string,\n wsKey: string | undefined,\n since: number,\n pollMs = 500,\n signal?: AbortSignal,\n ): AsyncGenerator<{ id: number; doc_id: string; op: string; ts: number }, void, void> {\n if (!this.namespaces.has(namespace)) {\n return\n }\n\n const persistence = this.resolve(namespace)\n\n if (!supportsChangeTracking(persistence)) {\n return\n }\n\n let cursor = since\n let lastId = -1\n while (!signal?.aborted) {\n await delay(pollMs, signal)\n if (signal?.aborted) break\n\n const rows = persistence.getChangeLog(namespace, cursor)\n\n const prefix2 = `${wsKey || 'default'}/`\n\n for (const row of rows) {\n if (signal?.aborted) break\n if (row.id <= lastId) continue\n if (!row.doc_id.startsWith(prefix2)) continue\n\n yield { ...row, doc_id: row.doc_id.slice(prefix2.length) }\n cursor = Math.max(cursor, row.ts)\n lastId = Math.max(lastId, row.id)\n }\n }\n }\n\n private resolve(namespace: string): Persistence<unknown> {\n if (!this.namespaces.has(namespace)) {\n throw new Error(`Unknown storage namespace: ${namespace}`)\n }\n\n let instance = this.instances.get(namespace)\n if (!instance) {\n instance = this.backend(namespace)\n this.instances.set(namespace, instance)\n }\n return instance\n }\n}\n\nexport function createFileStorageHost(options: {\n baseDir: string\n namespaces?: string[]\n now?: () => number\n}): StorageHost {\n return new StorageHost({\n namespaces: options.namespaces ?? DEFAULT_NAMESPACES,\n backend: (namespace) => new FilePersistence({ baseDir: join(options.baseDir, namespace) }),\n now: options.now,\n })\n}\n\nfunction delay(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve) => {\n if (signal?.aborted) {\n resolve()\n return\n }\n const t = setTimeout(resolve, ms)\n if (signal) {\n signal.addEventListener(\n 'abort',\n () => {\n clearTimeout(t)\n resolve()\n },\n { once: true },\n )\n }\n })\n}\n","import type { AppendLog, AppendRange, LogCodec, LogEntry } from './append-log'\n\nexport interface HttpAppendLogOptions<E> {\n /** remote-persistence-server base URL (e.g. https://storage.example.com). */\n baseUrl: string\n /** Auth token provider (same as RemotePersistence). */\n getAuth: () => Promise<{ token: string }>\n /** Optional extra headers (e.g. X-Agent-Id). */\n getHeaders?: () => Promise<Record<string, string>>\n /** fetch impl (defaults to globalThis.fetch). */\n fetch?: typeof globalThis.fetch\n /** Request timeout ms (default 30s). */\n timeoutMs?: number\n /** Batch flush interval ms (default 2000). */\n flushIntervalMs?: number\n /** Max batch size before immediate flush (default 100). */\n flushAtSize?: number\n /** Poll interval ms for tail (default 500). */\n pollIntervalMs?: number\n /** Codec for encoding/decoding entries (default JSON). */\n codec?: LogCodec<E>\n}\n\n/**\n * HTTP AppendLog 实现 — 将 trace/checkpoint 事件推送到 remote-persistence-server。\n *\n * 内部批处理(定时或满批量 flush),避免每事件一次 HTTP round-trip。\n * append 是 fire-and-forget(调用方只关心事件已入队);flush 在后台异步完成。\n *\n * read/tail 直接拉取服务端已持久化数据;tail 采用轮询模式。\n */\nexport class HttpAppendLog<E> implements AppendLog<E> {\n private readonly baseUrl: string\n private readonly getAuth: () => Promise<{ token: string }>\n private readonly getHeaders: () => Promise<Record<string, string>>\n private readonly fetchImpl: typeof globalThis.fetch\n private readonly timeoutMs: number\n private readonly pollIntervalMs: number\n private readonly flushAtSize: number\n private readonly codec: LogCodec<E>\n private readonly buffer = new Map<\n string,\n { encoded: string; resolve: (entry: LogEntry<E>) => void; reject: (err: unknown) => void }[]\n >()\n /** RFC-353: 正在 flush 的 streamId 集合——防止满批量 flush 与定时 flush 并发发同一 batch。 */\n private readonly flushingStreams = new Set<string>()\n private flushTimer?: ReturnType<typeof setInterval>\n private closed = false\n\n constructor(options: HttpAppendLogOptions<E>) {\n this.baseUrl = options.baseUrl.replace(/\\/+$/, '')\n this.getAuth = options.getAuth\n this.getHeaders = options.getHeaders ?? (async () => ({}))\n this.fetchImpl = options.fetch ?? globalThis.fetch\n this.timeoutMs = options.timeoutMs ?? 30_000\n this.pollIntervalMs = options.pollIntervalMs ?? 500\n this.flushAtSize = options.flushAtSize ?? 100\n this.codec = options.codec ?? {\n encode: (entry: E) => JSON.stringify(entry),\n decode: (payload: string) => JSON.parse(payload) as E,\n }\n\n const intervalMs = options.flushIntervalMs ?? 2000\n this.flushTimer = setInterval(() => {\n this.flushAll().catch(() => {})\n }, intervalMs)\n this.flushTimer.unref?.()\n }\n\n async append(streamId: string, entry: E): Promise<LogEntry<E>> {\n const encoded = this.codec.encode(entry)\n\n return new Promise((resolve, reject) => {\n if (this.closed) {\n reject(new Error('HttpAppendLog closed — buffered events lost'))\n return\n }\n const buf = this.buffer.get(streamId) ?? []\n buf.push({ encoded, resolve, reject })\n this.buffer.set(streamId, buf)\n\n if (buf.length >= this.flushAtSize) {\n this.tryFlush(streamId).catch(() => {})\n }\n })\n }\n\n /**\n * RFC-353: per-stream flush 串行化。若该 stream 已在 flush 中(满批量触发 + 定时触发\n * 或两次满批量触发重叠),跳过——正在进行的 flush 完成后,新 buffer 会在下一轮\n * 定时 flush 或下次满批量时被处理。消除并发 flushOne 同一 batch 导致服务端重复 append\n * 与 seq 不连续。\n */\n private async tryFlush(streamId: string): Promise<void> {\n if (this.flushingStreams.has(streamId)) return\n const batch = this.buffer.get(streamId)\n if (!batch || batch.length === 0) return\n this.flushingStreams.add(streamId)\n try {\n await this.flushOne(streamId, batch)\n } finally {\n this.flushingStreams.delete(streamId)\n }\n }\n\n private async flushOne(\n streamId: string,\n batch: {\n encoded: string\n resolve: (entry: LogEntry<E>) => void\n reject: (err: unknown) => void\n }[],\n ): Promise<void> {\n this.buffer.delete(streamId)\n\n try {\n const { seqs } = await this.request<{ seqs: number[] }>(\n `/trace/${encodeURIComponent(streamId)}`,\n 'POST',\n batch.map((b) => b.encoded),\n )\n\n for (let i = 0; i < batch.length; i++) {\n const seq = seqs[i] ?? -1\n const entry = this.codec.decode(batch[i]!.encoded)\n batch[i]!.resolve({ seq, streamId, entry })\n }\n } catch {\n if (this.closed) {\n for (const item of batch) {\n item.reject(new Error('HttpAppendLog closed — buffered events lost'))\n }\n } else {\n const existing = this.buffer.get(streamId) ?? []\n this.buffer.set(streamId, [...existing, ...batch])\n }\n }\n }\n\n private async flushAll(): Promise<void> {\n const streams = [...this.buffer.keys()]\n for (const streamId of streams) {\n // RFC-353: 跳过正在 flush 的 stream——正在进行的 flush 完成后,\n // 新 buffer 会在下一轮定时 flush 时被处理。\n if (this.flushingStreams.has(streamId)) continue\n const batch = this.buffer.get(streamId)\n if (batch && batch.length > 0) {\n this.flushingStreams.add(streamId)\n try {\n await this.flushOne(streamId, batch)\n } finally {\n this.flushingStreams.delete(streamId)\n }\n }\n }\n }\n\n async *read(streamId: string, range?: AppendRange): AsyncIterable<LogEntry<E>> {\n const params = new URLSearchParams()\n if (range?.from !== undefined) params.set('from', String(range.from))\n if (range?.to !== undefined) params.set('to', String(range.to))\n\n const query = params.toString()\n const path = `/trace/${encodeURIComponent(streamId)}${query ? `?${query}` : ''}`\n const { events } = await this.request<{ events: string[] }>(path, 'GET')\n\n for (let i = 0; i < events.length; i++) {\n const entry = this.codec.decode(events[i]!)\n const seq = (range?.from ?? 0) + i\n yield { seq, streamId, entry }\n }\n }\n\n async *tail(streamId: string): AsyncIterable<LogEntry<E>> {\n let lastSeq = -1\n {\n const path = `/trace/${encodeURIComponent(streamId)}`\n const { events } = await this.request<{ events: string[] }>(path, 'GET')\n\n for (let i = 0; i < events.length; i++) {\n const entry = this.codec.decode(events[i]!)\n const seq = i\n lastSeq = seq\n yield { seq, streamId, entry }\n }\n }\n\n while (!this.closed) {\n const from = lastSeq + 1\n const path = `/trace/${encodeURIComponent(streamId)}?from=${from}`\n try {\n const { events } = await this.request<{ events: string[] }>(path, 'GET')\n\n for (let i = 0; i < events.length; i++) {\n const entry = this.codec.decode(events[i]!)\n const seq = from + i\n lastSeq = seq\n yield { seq, streamId, entry }\n }\n } catch {}\n\n await delay(this.pollIntervalMs)\n }\n }\n\n /**\n * stream 事件总数(服务端 COUNT(*) 索引扫描 + 本地未 flush 的在途 buffer 计数)。\n * 消费端(inspector backfill)据此只拉最近 N 条,不必先 read() 全量再取 length\n * (RFC-102 backlog T4:此前无 count() 时 `?? 0` 回退触发全量回放)。\n */\n async count(streamId: string): Promise<number> {\n const { count } = await this.request<{ count: number }>(\n `/trace/${encodeURIComponent(streamId)}/count`,\n 'GET',\n )\n return count + (this.buffer.get(streamId)?.length ?? 0)\n }\n\n async truncate(streamId: string, toSeq: number): Promise<void> {\n await this.request(`/trace/${encodeURIComponent(streamId)}/truncate`, 'POST', { toSeq })\n }\n\n async clear(streamId: string): Promise<void> {\n await this.request(`/trace/${encodeURIComponent(streamId)}`, 'DELETE')\n }\n\n /** 关闭:停止 flush 定时器,排空 buffer。 */\n async close(): Promise<void> {\n this.closed = true\n if (this.flushTimer) {\n clearInterval(this.flushTimer)\n this.flushTimer = undefined\n }\n await this.flushAll()\n }\n\n private async request<T>(path: string, method: string, body?: unknown): Promise<T> {\n const auth = await this.getAuth()\n\n const headers: Record<string, string> = {\n ...(await this.getHeaders()),\n ...(auth.token ? { Authorization: `Bearer ${auth.token}` } : {}),\n Accept: 'application/json',\n }\n\n if (body !== undefined) {\n headers['Content-Type'] = 'application/json'\n }\n\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), this.timeoutMs)\n\n const url = `${this.baseUrl}${path}`\n\n try {\n const response = await this.fetchImpl(url, {\n method,\n headers,\n body: body !== undefined ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n })\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`)\n }\n\n return (await response.json()) as T\n } finally {\n clearTimeout(timeout)\n }\n }\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => {\n // unref:tail() 轮询用此 delay;与 FileAppendLog 一致——不 unref 会让常驻 tail 吊住事件循环阻止退出。\n const timer = setTimeout(resolve, ms)\n timer.unref?.()\n })\n}\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@x-otto/persistence",
3
+ "version": "0.0.1-alpha.0",
4
+ "files": [
5
+ "dist"
6
+ ],
7
+ "type": "module",
8
+ "main": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js"
14
+ }
15
+ },
16
+ "publishConfig": {
17
+ "access": "public",
18
+ "registry": "https://registry.npmjs.org",
19
+ "tag": "alpha"
20
+ },
21
+ "dependencies": {
22
+ "@x-otto/env": "0.1.0-alpha.1",
23
+ "better-sqlite3": "^12.9.0"
24
+ },
25
+ "private": false,
26
+ "scripts": {
27
+ "build": "tsdown",
28
+ "typecheck:project": "tsc -p tsconfig.json --noEmit",
29
+ "typecheck:file": "tsc src/index.ts --noEmit --target ES2024 --module ESNext --moduleResolution bundler --esModuleInterop --skipLibCheck",
30
+ "typecheck": "tsc --noEmit",
31
+ "clean": "rm -rf dist"
32
+ }
33
+ }