@morlay/session-rdb 0.0.15 → 0.0.16-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +11 -13
  2. package/dist/artifact.d.mts +30 -16
  3. package/dist/artifact.mjs +3 -3
  4. package/dist/{import-DFed8DBt.mjs → import-mZZgDBLd.mjs} +73 -83
  5. package/dist/import.d.mts +2 -3
  6. package/dist/import.mjs +2 -2
  7. package/dist/index-GdKkSSb-.d.mts +239 -0
  8. package/dist/index.d.mts +3 -3
  9. package/dist/index.mjs +763 -168
  10. package/dist/log-DBFUBPhv.mjs +394 -0
  11. package/dist/{schema-CFXZURX7.d.mts → schema-COK7-wRV.d.mts} +3 -15
  12. package/dist/sqlite-CnWwGToZ.mjs +698 -0
  13. package/dist/storage.d.mts +2 -7
  14. package/dist/storage.mjs +2 -3
  15. package/dist/testing.d.mts +30 -1
  16. package/dist/testing.mjs +676 -1991
  17. package/drizzle/postgres/20260908072805_v2_initial/migration.sql +58 -0
  18. package/drizzle/postgres/20260908072805_v2_initial/snapshot.json +686 -0
  19. package/drizzle/postgres/20260908072806_v3_drop_original_seq/migration.sql +1 -0
  20. package/drizzle/postgres/20260908072806_v3_drop_original_seq/snapshot.json +673 -0
  21. package/drizzle/sqlite/20260908072751_v2_initial/migration.sql +53 -0
  22. package/drizzle/sqlite/20260908072751_v2_initial/snapshot.json +492 -0
  23. package/drizzle/sqlite/20260908072752_v3_drop_original_seq/migration.sql +6 -0
  24. package/drizzle/sqlite/20260908072752_v3_drop_original_seq/snapshot.json +513 -0
  25. package/package.json +17 -12
  26. package/src/adapters/index.ts +10 -2
  27. package/src/adapters/to-postgres.ts +19 -15
  28. package/src/adapters/to-sqlite.ts +20 -14
  29. package/src/{entities → adapters}/types.ts +7 -8
  30. package/src/backend.ts +0 -7
  31. package/src/branch.ts +29 -110
  32. package/src/drizzle/postgres-v2.ts +11 -0
  33. package/src/drizzle/postgres-v3.ts +11 -0
  34. package/src/drizzle/sqlite-v2.ts +10 -0
  35. package/src/drizzle/sqlite-v3.ts +11 -0
  36. package/src/entities/index.ts +2 -30
  37. package/src/entities/v2/index.ts +20 -0
  38. package/src/entities/v2/session-events.ts +11 -0
  39. package/src/entities/v3/events.ts +27 -0
  40. package/src/entities/v3/index.ts +27 -0
  41. package/src/entities/v3/persistence-state.ts +10 -0
  42. package/src/entities/v3/schema-meta.ts +9 -0
  43. package/src/entities/v3/session-events.ts +26 -0
  44. package/src/entities/v3/sessions.ts +20 -0
  45. package/src/import.ts +65 -57
  46. package/src/index.ts +929 -227
  47. package/src/invariant.ts +0 -2
  48. package/src/legacy.ts +110 -0
  49. package/src/log.ts +143 -91
  50. package/src/postgres.ts +75 -93
  51. package/src/schema.ts +3 -14
  52. package/src/sqlite.ts +59 -46
  53. package/src/testing/contract.ts +460 -375
  54. package/src/testing/coordinator-contract.ts +108 -1374
  55. package/src/testing.ts +1 -6
  56. package/dist/index-uUvn6gYp.d.mts +0 -111
  57. package/dist/schema-vwzwEXNE.mjs +0 -878
  58. package/dist/sqlite-CG_Qcx5C.mjs +0 -356
  59. package/src/adapters/ddl.ts +0 -77
  60. package/src/entities/events.ts +0 -27
  61. package/src/entities/persistence-state.ts +0 -10
  62. package/src/entities/schema-meta.ts +0 -9
  63. package/src/entities/session-events.ts +0 -29
  64. package/src/entities/sessions.ts +0 -20
  65. package/src/migrate.ts +0 -137
package/src/import.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import type { Context } from "@deepseek-ai/cordis";
3
- import { SessionLogOffset, decodeSeqRanges, decodeStorageRecord } from "@deepseek-ai/dsh-session";
4
- import type { Session, SessionEvent, SessionId } from "@deepseek-ai/dsh-session";
3
+ import { SESSION_FORMAT_VERSION, SessionLogOffset } from "@deepseek-ai/dsh-session";
4
+ import type { Session, SessionEvent, SessionId, SessionHeader } from "@deepseek-ai/dsh-session";
5
+ import { sessionFormatCatalog } from "@deepseek-ai/dsh-session-format-catalog";
5
6
  import type { SessionStorageMetadata } from "@deepseek-ai/dsh-session-persistence";
6
7
  import { unzipSync } from "fflate";
7
8
  import { replaceLiveSessionLog } from "./branch.ts";
@@ -13,21 +14,6 @@ export const SESSION_IMPORT_PATH = "/api/session.import";
13
14
 
14
15
  const MAX_IMPORT_ZIP_BYTES = 64 * 1024 * 1024;
15
16
 
16
- export function expandProvenanceFromStorage(parsed: unknown): unknown {
17
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
18
- throw new TypeError("imported session records must be objects");
19
- }
20
- const record = parsed as { seq?: unknown; sourceEventSeqs?: unknown };
21
- if (record.sourceEventSeqs === undefined) return parsed;
22
- if (!Number.isSafeInteger(record.seq) || (record.seq as number) < 0) {
23
- throw new TypeError("imported session event seq must be a non-negative safe integer");
24
- }
25
- return {
26
- ...record,
27
- sourceEventSeqs: decodeSeqRanges(record.sourceEventSeqs, (record.seq as number) + 1),
28
- };
29
- }
30
-
31
17
  export function parseJsonlArtifact(content: string): SessionStorageMetadata & {
32
18
  events: SessionEvent[];
33
19
  } {
@@ -35,31 +21,24 @@ export function parseJsonlArtifact(content: string): SessionStorageMetadata & {
35
21
  if (lines.length === 0 || lines[0] === "") {
36
22
  throw new Error("imported session log is empty");
37
23
  }
38
- let header: Record<string, unknown> | undefined;
24
+ let header: unknown;
39
25
  try {
40
- header = JSON.parse(lines[0] as string) as Record<string, unknown>;
26
+ header = JSON.parse(lines[0] as string) as unknown;
41
27
  } catch {
42
28
  throw new Error("imported session log has an unparsable header line");
43
29
  }
44
- if (
45
- typeof header !== "object" ||
46
- header === null ||
47
- header["type"] !== "session" ||
48
- typeof header["id"] !== "string" ||
49
- typeof header["version"] !== "number" ||
50
- !Number.isSafeInteger(header["createdAt"] as number) ||
51
- (header["createdAt"] as number) < 0
52
- ) {
53
- throw new Error("imported session log has an invalid header line");
54
- }
55
- const seedLength = header["seedLength"];
56
- if (
57
- seedLength !== undefined &&
58
- (!Number.isSafeInteger(seedLength) || (seedLength as number) < 0)
59
- ) {
60
- throw new Error("imported session log has an invalid seedLength");
30
+ // 经 format catalog 恢复:v2 直接解码,历史版本走迁移链。
31
+ let restore: ReturnType<typeof sessionFormatCatalog.createRestore>;
32
+ try {
33
+ restore = sessionFormatCatalog.createRestore(header, {
34
+ recovery: "strict",
35
+ validation: "transformed",
36
+ });
37
+ } catch (error: unknown) {
38
+ throw new Error(
39
+ `imported session log has an invalid header line: ${error instanceof Error ? error.message : String(error)}`,
40
+ );
61
41
  }
62
- const events: SessionEvent[] = [];
63
42
  for (let i = 1; i < lines.length; i++) {
64
43
  const line = lines[i];
65
44
  if (line === undefined || line === "") continue;
@@ -69,10 +48,28 @@ export function parseJsonlArtifact(content: string): SessionStorageMetadata & {
69
48
  } catch {
70
49
  throw new Error(`imported session log has an unparsable event line at ${i}`);
71
50
  }
72
- for (const event of decodeStorageRecord(expandProvenanceFromStorage(parsed))) {
73
- events.push(event);
51
+ try {
52
+ restore.decodeRow(parsed);
53
+ } catch (error: unknown) {
54
+ throw new Error(
55
+ `imported session log has an invalid event line at ${i}: ${error instanceof Error ? error.message : String(error)}`,
56
+ );
74
57
  }
75
58
  }
59
+ let artifact: { header: unknown; inheritedEventCount: number; events: readonly unknown[] };
60
+ try {
61
+ artifact = restore.finish() as unknown as {
62
+ header: unknown;
63
+ inheritedEventCount: number;
64
+ events: readonly unknown[];
65
+ };
66
+ } catch (error: unknown) {
67
+ throw new Error(
68
+ `imported session log is incomplete: ${error instanceof Error ? error.message : String(error)}`,
69
+ );
70
+ }
71
+ const meta = artifact.header as SessionHeader;
72
+ const events = artifact.events as SessionEvent[];
76
73
  // 连续性校验:导入的 log 必须是从 0 开始的稠密 seq(落库前的最后一道闸)。
77
74
  for (let i = 0; i < events.length; i++) {
78
75
  if (events[i]!.seq !== i) {
@@ -81,26 +78,20 @@ export function parseJsonlArtifact(content: string): SessionStorageMetadata & {
81
78
  );
82
79
  }
83
80
  }
84
- const origin = header["origin"];
85
- const delegationDepth = header["delegationDepth"];
86
81
  // 继承前缀长度不得超过事件总数(上游 load 判损坏);导出已收缩,此处
87
82
  // 防御性收缩非自洽的 artifact。
88
- const inheritedEventCount = Math.min((seedLength as number | undefined) ?? 0, events.length);
83
+ const inheritedEventCount = Math.min(artifact.inheritedEventCount, events.length);
89
84
  return {
90
85
  meta: {
91
- version: header["version"] as number,
92
- id: header["id"] as SessionId,
93
- createdAt: header["createdAt"] as number,
94
- ...(typeof header["cwd"] === "string" ? { cwd: header["cwd"] } : {}),
95
- ...(typeof header["parentSession"] === "string"
96
- ? { parentSession: header["parentSession"] as SessionId }
97
- : {}),
98
- isSeeded: seedLength !== undefined,
99
- ...(origin === "subagent" ? { origin: origin as "subagent" } : {}),
100
- ...(Number.isSafeInteger(delegationDepth as number) && (delegationDepth as number) > 0
101
- ? { delegationDepth: delegationDepth as number }
102
- : {}),
103
- ...(typeof header["agentPreset"] === "string" ? { agentPreset: header["agentPreset"] } : {}),
86
+ version: SESSION_FORMAT_VERSION,
87
+ id: meta.id,
88
+ createdAt: meta.createdAt,
89
+ ...(meta.cwd === undefined ? {} : { cwd: meta.cwd }),
90
+ ...(meta.parentSession === undefined ? {} : { parentSession: meta.parentSession }),
91
+ isSeeded: meta.isSeeded,
92
+ ...(meta.origin === undefined ? {} : { origin: meta.origin }),
93
+ ...(meta.delegationDepth === undefined ? {} : { delegationDepth: meta.delegationDepth }),
94
+ ...(meta.agentPreset === undefined ? {} : { agentPreset: meta.agentPreset }),
104
95
  },
105
96
  inheritedEventCount: SessionLogOffset(inheritedEventCount),
106
97
  events,
@@ -136,10 +127,27 @@ export async function persistImport(
136
127
  throw new Error("sessionBranch service is unavailable");
137
128
  }
138
129
  await branch.rewind(targetId, -1);
130
+ // rewind 截断后追加导入事件(覆盖语义)。live 会话的 write handle 由
131
+ // live 路由持有——复用而非 open(open 会撞 SessionAlreadyOwnedError)。
132
+ const liveHandle = persistence.tracker.writerOf(targetId);
133
+ if (liveHandle !== undefined) {
134
+ if (imported.events.length > 0) await liveHandle.append(imported.events);
135
+ } else {
136
+ const handle = await persistence.open(targetId, "write");
137
+ try {
138
+ if (imported.events.length > 0) await handle.append(imported.events);
139
+ } finally {
140
+ await handle.close();
141
+ }
142
+ }
139
143
  } else {
140
- await persistence.create({ ...imported.meta, id }, imported.inheritedEventCount);
144
+ const handle = await persistence.create(
145
+ { ...imported.meta, id },
146
+ { inheritedEventCount: imported.inheritedEventCount },
147
+ );
148
+ if (imported.events.length > 0) await handle.append(imported.events);
149
+ await handle.close();
141
150
  }
142
- if (imported.events.length > 0) await persistence.append(id, imported.events);
143
151
  // 覆盖语义的 live 同步:rewind 截断的 live log 由同一批导入事件补回
144
152
  // (不发布、不落库),使 observeSession 的 live 快照与 DB 一致。
145
153
  if (targetId !== undefined) {