@morlay/session-branch 0.0.10-alpha.3 → 0.0.10-alpha.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -9
- package/dist/index.d.mts +2 -24
- package/dist/index.mjs +15 -77
- package/package.json +1 -1
- package/src/branch.ts +1 -3
- package/src/index.ts +0 -2
- package/src/types.ts +3 -21
- package/src/timeline.ts +0 -120
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# @morlay/session-branch
|
|
2
2
|
|
|
3
3
|
分支式会话编辑的**契约层**:定义数据层分支原语 `SessionBranchProvider`
|
|
4
|
-
(rewind / forkFrom / readBranchPrefix
|
|
5
|
-
(`ctx.sessionBranch
|
|
4
|
+
(rewind / forkFrom / readBranchPrefix)与高层服务 `SessionBranch`
|
|
5
|
+
(`ctx.sessionBranch`)。
|
|
6
6
|
|
|
7
7
|
跨包术语见根 [`.agents/CONTEXT.md`](../../../.agents/CONTEXT.md),本包的决策见 [`.agents/adrs/`](./.agents/adrs)。
|
|
8
8
|
|
|
@@ -38,11 +38,8 @@ interface SessionBranchProvider {
|
|
|
38
38
|
(内存 log 截断 + 派生缓存复位 + handle cursor / 继承前缀对齐,详见
|
|
39
39
|
`@morlay/session-rdb` 的 [分支能力](../session-rdb/.agents/designs/20260917-分支能力.md))。
|
|
40
40
|
|
|
41
|
-
##
|
|
41
|
+
## 版本效果(历史形状)
|
|
42
42
|
|
|
43
|
-
`
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
版本效果事件携带 `ignorable: true` 并**原样落库**,因此 cold 会话也能恢复效果详情
|
|
48
|
-
(见 [ADR-版本效果以ignorable事件原样落库](./.agents/adrs/20260917-版本效果以ignorable事件原样落库.md))。
|
|
43
|
+
`session-branch/version` 事件的类型定义仍在本包(`types.ts`),用于识别旧会话里
|
|
44
|
+
已落库的历史事件;**写侧与读侧都已删除**(不再产出新事件、没有读者),理由见
|
|
45
|
+
[ADR-删除版本树投影并停止写版本效果](./.agents/adrs/20260920-删除版本树投影并停止写版本效果.md)。
|
package/dist/index.d.mts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { Context, Service } from "@deepseek-ai/cordis";
|
|
2
|
-
import { SessionEvent,
|
|
2
|
+
import { SessionEvent, SessionId } from "@deepseek-ai/dsh-session";
|
|
3
3
|
import { SessionPersistenceSnapshot } from "@deepseek-ai/dsh-session-persistence";
|
|
4
4
|
//#region src/types.d.ts
|
|
5
|
-
type _BranchKeyCheck = "session-branch/version" extends keyof SessionEventMap ? true : false;
|
|
6
|
-
declare const _branchKeyVisible: _BranchKeyCheck;
|
|
7
5
|
declare const SESSION_BRANCH_VERSION_SCHEMA = 1;
|
|
8
6
|
type CascadePolicy = "truncate" | "preserve";
|
|
9
7
|
type VersionOperation = "edit" | "reroll" | "retry" | "fork" | "rewind";
|
|
@@ -51,18 +49,6 @@ interface ForkFromOptions {
|
|
|
51
49
|
childSessionId?: SessionId;
|
|
52
50
|
meta?: BranchForkMeta;
|
|
53
51
|
}
|
|
54
|
-
interface BranchVersionNode {
|
|
55
|
-
sessionId: SessionId;
|
|
56
|
-
parentSessionId?: SessionId;
|
|
57
|
-
seedLength: number;
|
|
58
|
-
createdAt: number;
|
|
59
|
-
effect?: SessionBranchEffect;
|
|
60
|
-
inverseSessionId?: SessionId;
|
|
61
|
-
}
|
|
62
|
-
interface BranchTimeline {
|
|
63
|
-
root: BranchVersionNode;
|
|
64
|
-
nodes: BranchVersionNode[];
|
|
65
|
-
}
|
|
66
52
|
type SessionBranchErrorCode = "SESSION_NOT_FOUND" | "INVALID_BOUNDARY" | "OPEN_TURN" | "FORK_UNAVAILABLE" | "REWIND_CONFLICT";
|
|
67
53
|
declare class SessionBranchError extends Error {
|
|
68
54
|
readonly code: SessionBranchErrorCode;
|
|
@@ -95,16 +81,8 @@ declare abstract class SessionBranch extends Service {
|
|
|
95
81
|
abstract readBranchPrefix(id: SessionId, atSeq?: number, mode?: BranchAnchorMode, signal?: AbortSignal): Promise<BranchBoundary>;
|
|
96
82
|
abstract forkFrom(sourceId: SessionId, options?: ForkFromOptions, signal?: AbortSignal): Promise<SessionId>;
|
|
97
83
|
abstract rewind(id: SessionId, toBoundary: number, signal?: AbortSignal): Promise<SessionPersistenceSnapshot>;
|
|
98
|
-
abstract timeline(sessionId: SessionId, signal?: AbortSignal): Promise<BranchTimeline>;
|
|
99
84
|
}
|
|
100
85
|
//#endregion
|
|
101
|
-
//#region src/timeline.d.ts
|
|
102
|
-
type BranchSnapshot = SessionPersistenceSnapshot & {
|
|
103
|
-
inheritedEventCount: number;
|
|
104
|
-
};
|
|
105
|
-
type OwnEventsReader = (id: SessionId, fromSeq: number, signal?: AbortSignal) => Promise<readonly import("@deepseek-ai/dsh-session").SessionEvent[]>;
|
|
106
|
-
declare function buildTimeline(snapshots: readonly BranchSnapshot[], readOwnEvents: OwnEventsReader, sessionId: SessionId, signal?: AbortSignal): Promise<BranchTimeline>;
|
|
107
|
-
//#endregion
|
|
108
86
|
//#region src/balance.d.ts
|
|
109
87
|
interface BalanceRewindPrefixOptions {
|
|
110
88
|
keepOpenTail?: boolean;
|
|
@@ -120,4 +98,4 @@ declare module "@deepseek-ai/cordis" {
|
|
|
120
98
|
}
|
|
121
99
|
declare function apply(_ctx: Context): void;
|
|
122
100
|
//#endregion
|
|
123
|
-
export { type BranchAnchorMode, BranchBoundary, BranchForkMeta,
|
|
101
|
+
export { type BranchAnchorMode, BranchBoundary, BranchForkMeta, CascadePolicy, EditableBlockKind, ForkFromOptions, SESSION_BRANCH_VERSION_SCHEMA, SessionBranch, SessionBranchEffect, SessionBranchError, SessionBranchErrorCode, SessionBranchInverse, type SessionBranchProvider, SessionBranchVersionEvent, SessionBranchVersionEventEnvelope, VersionOperation, apply, balanceRewindPrefix, isSessionBranchVersionEvent, rewindKeepLength };
|
package/dist/index.mjs
CHANGED
|
@@ -6,82 +6,6 @@ var SessionBranch = class extends Service {
|
|
|
6
6
|
}
|
|
7
7
|
};
|
|
8
8
|
//#endregion
|
|
9
|
-
//#region src/types.ts
|
|
10
|
-
const _branchKeyVisible = true;
|
|
11
|
-
const SESSION_BRANCH_VERSION_SCHEMA = 1;
|
|
12
|
-
var SessionBranchError = class extends Error {
|
|
13
|
-
code;
|
|
14
|
-
constructor(message, code) {
|
|
15
|
-
super(message);
|
|
16
|
-
this.name = "SessionBranchError";
|
|
17
|
-
this.code = code;
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
function isSessionBranchVersionEvent(event) {
|
|
21
|
-
return event.type === "session-branch/version" && event.data.schemaVersion === 1;
|
|
22
|
-
}
|
|
23
|
-
//#endregion
|
|
24
|
-
//#region src/timeline.ts
|
|
25
|
-
async function buildTimeline(snapshots, readOwnEvents, sessionId, signal) {
|
|
26
|
-
const byId = new Map(snapshots.map((snapshot) => [snapshot.header.id, snapshot]));
|
|
27
|
-
const ancestors = [];
|
|
28
|
-
let cursor = sessionId;
|
|
29
|
-
const seen = /* @__PURE__ */ new Set();
|
|
30
|
-
while (cursor !== void 0) {
|
|
31
|
-
if (seen.has(cursor)) throw new SessionBranchError("lineage contains a cycle", "INVALID_BOUNDARY");
|
|
32
|
-
seen.add(cursor);
|
|
33
|
-
ancestors.push(cursor);
|
|
34
|
-
cursor = byId.get(cursor)?.header.parentSession;
|
|
35
|
-
}
|
|
36
|
-
const rootId = ancestors.at(-1);
|
|
37
|
-
if (rootId === void 0) throw new SessionBranchError(`session "${sessionId}" is not persisted`, "SESSION_NOT_FOUND");
|
|
38
|
-
const ordered = [];
|
|
39
|
-
const queue = [rootId];
|
|
40
|
-
while (queue.length > 0) {
|
|
41
|
-
const id = queue.shift();
|
|
42
|
-
if (id === void 0) continue;
|
|
43
|
-
ordered.push(id);
|
|
44
|
-
const children = snapshots.filter((snapshot) => snapshot.header.parentSession === id).sort((left, right) => left.header.createdAt - right.header.createdAt || String(left.header.id).localeCompare(String(right.header.id))).map((snapshot) => snapshot.header.id);
|
|
45
|
-
queue.push(...children);
|
|
46
|
-
}
|
|
47
|
-
const nodes = [];
|
|
48
|
-
const effectIds = /* @__PURE__ */ new Set();
|
|
49
|
-
for (const id of ordered) {
|
|
50
|
-
const snapshot = byId.get(id);
|
|
51
|
-
if (snapshot === void 0) continue;
|
|
52
|
-
const header = snapshot.header;
|
|
53
|
-
const node = {
|
|
54
|
-
sessionId: header.id,
|
|
55
|
-
...header.parentSession === void 0 ? {} : { parentSessionId: header.parentSession },
|
|
56
|
-
seedLength: snapshot.inheritedEventCount ?? 0,
|
|
57
|
-
createdAt: header.createdAt
|
|
58
|
-
};
|
|
59
|
-
if (header.parentSession !== void 0) {
|
|
60
|
-
const events = await readOwnEvents(header.id, node.seedLength, signal);
|
|
61
|
-
let version;
|
|
62
|
-
for (const event of events) if (isSessionBranchVersionEvent(event)) {
|
|
63
|
-
if (version !== void 0) throw new SessionBranchError(`session ${header.id} carries multiple own version effects`, "INVALID_BOUNDARY");
|
|
64
|
-
version = event;
|
|
65
|
-
}
|
|
66
|
-
if (version !== void 0) {
|
|
67
|
-
const data = version.data;
|
|
68
|
-
if (data.inverse.kind !== "restore-version" || data.inverse.sessionId !== header.parentSession) throw new SessionBranchError(`session ${header.id} version inverse does not match its parent`, "INVALID_BOUNDARY");
|
|
69
|
-
if (effectIds.has(data.effect.id)) throw new SessionBranchError(`version effect ${data.effect.id} is duplicated`, "INVALID_BOUNDARY");
|
|
70
|
-
effectIds.add(data.effect.id);
|
|
71
|
-
node.effect = data.effect;
|
|
72
|
-
node.inverseSessionId = data.inverse.sessionId;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
nodes.push(node);
|
|
76
|
-
}
|
|
77
|
-
const root = nodes.find((node) => node.parentSessionId === void 0);
|
|
78
|
-
if (root === void 0) throw new SessionBranchError(`session "${sessionId}" lineage has no root`, "SESSION_NOT_FOUND");
|
|
79
|
-
return {
|
|
80
|
-
root,
|
|
81
|
-
nodes
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
//#endregion
|
|
85
9
|
//#region src/balance.ts
|
|
86
10
|
function balanceRewindPrefix(events, options = {}) {
|
|
87
11
|
let keep = events.length;
|
|
@@ -131,7 +55,21 @@ function rewindKeepLength(types, rawKeepLength) {
|
|
|
131
55
|
return Math.max(0, rawKeepLength - types.length + relativeKeep);
|
|
132
56
|
}
|
|
133
57
|
//#endregion
|
|
58
|
+
//#region src/types.ts
|
|
59
|
+
const SESSION_BRANCH_VERSION_SCHEMA = 1;
|
|
60
|
+
var SessionBranchError = class extends Error {
|
|
61
|
+
code;
|
|
62
|
+
constructor(message, code) {
|
|
63
|
+
super(message);
|
|
64
|
+
this.name = "SessionBranchError";
|
|
65
|
+
this.code = code;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
function isSessionBranchVersionEvent(event) {
|
|
69
|
+
return event.type === "session-branch/version" && event.data.schemaVersion === 1;
|
|
70
|
+
}
|
|
71
|
+
//#endregion
|
|
134
72
|
//#region src/index.ts
|
|
135
73
|
function apply(_ctx) {}
|
|
136
74
|
//#endregion
|
|
137
|
-
export { SESSION_BRANCH_VERSION_SCHEMA, SessionBranch, SessionBranchError,
|
|
75
|
+
export { SESSION_BRANCH_VERSION_SCHEMA, SessionBranch, SessionBranchError, apply, balanceRewindPrefix, isSessionBranchVersionEvent, rewindKeepLength };
|
package/package.json
CHANGED
package/src/branch.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Service } from "@deepseek-ai/cordis";
|
|
2
2
|
import type { SessionId } from "@deepseek-ai/dsh-session";
|
|
3
3
|
import type { SessionPersistenceSnapshot } from "@deepseek-ai/dsh-session-persistence";
|
|
4
|
-
import type { BranchBoundary,
|
|
4
|
+
import type { BranchBoundary, ForkFromOptions } from "./types.ts";
|
|
5
5
|
|
|
6
6
|
export abstract class SessionBranch extends Service {
|
|
7
7
|
constructor(ctx: import("@deepseek-ai/cordis").Context) {
|
|
@@ -26,6 +26,4 @@ export abstract class SessionBranch extends Service {
|
|
|
26
26
|
toBoundary: number,
|
|
27
27
|
signal?: AbortSignal,
|
|
28
28
|
): Promise<SessionPersistenceSnapshot>;
|
|
29
|
-
|
|
30
|
-
abstract timeline(sessionId: SessionId, signal?: AbortSignal): Promise<BranchTimeline>;
|
|
31
29
|
}
|
package/src/index.ts
CHANGED
|
@@ -3,8 +3,6 @@ import { SessionBranch } from "./branch.ts";
|
|
|
3
3
|
|
|
4
4
|
export { SessionBranch } from "./branch.ts";
|
|
5
5
|
export type { SessionBranchProvider, BranchAnchorMode } from "./provider.ts";
|
|
6
|
-
export { buildTimeline } from "./timeline.ts";
|
|
7
|
-
export type { OwnEventsReader } from "./timeline.ts";
|
|
8
6
|
export { balanceRewindPrefix, rewindKeepLength } from "./balance.ts";
|
|
9
7
|
export * from "./types.ts";
|
|
10
8
|
|
package/src/types.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import type { SessionEvent, SessionId } from "@deepseek-ai/dsh-session";
|
|
2
|
-
import type { SessionEventMap } from "@deepseek-ai/dsh-session";
|
|
3
|
-
|
|
4
|
-
type _BranchKeyCheck = "session-branch/version" extends keyof SessionEventMap ? true : false;
|
|
5
|
-
export const _branchKeyVisible: _BranchKeyCheck = true as const;
|
|
6
2
|
|
|
3
|
+
// `session-branch/version` 的**历史形状**:写侧(就地编辑产出)与读侧(版本树投影)都已删除
|
|
4
|
+
// (见 ADR 版本效果停止落库 / 版本树投影删除)。这里的定义只用于识别旧数据里已落库的
|
|
5
|
+
// ignorable 事件,不再有新事件产生、也没有读者。
|
|
7
6
|
export const SESSION_BRANCH_VERSION_SCHEMA = 1;
|
|
8
7
|
|
|
9
8
|
export type CascadePolicy = "truncate" | "preserve";
|
|
@@ -71,23 +70,6 @@ export interface ForkFromOptions {
|
|
|
71
70
|
meta?: BranchForkMeta;
|
|
72
71
|
}
|
|
73
72
|
|
|
74
|
-
export interface BranchVersionNode {
|
|
75
|
-
sessionId: SessionId;
|
|
76
|
-
parentSessionId?: SessionId;
|
|
77
|
-
|
|
78
|
-
seedLength: number;
|
|
79
|
-
createdAt: number;
|
|
80
|
-
|
|
81
|
-
effect?: SessionBranchEffect;
|
|
82
|
-
|
|
83
|
-
inverseSessionId?: SessionId;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export interface BranchTimeline {
|
|
87
|
-
root: BranchVersionNode;
|
|
88
|
-
nodes: BranchVersionNode[];
|
|
89
|
-
}
|
|
90
|
-
|
|
91
73
|
export type SessionBranchErrorCode =
|
|
92
74
|
| "SESSION_NOT_FOUND"
|
|
93
75
|
| "INVALID_BOUNDARY"
|
package/src/timeline.ts
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import type { SessionId } from "@deepseek-ai/dsh-session";
|
|
2
|
-
import type { SessionPersistenceSnapshot } from "@deepseek-ai/dsh-session-persistence";
|
|
3
|
-
import {
|
|
4
|
-
SessionBranchError,
|
|
5
|
-
isSessionBranchVersionEvent,
|
|
6
|
-
type BranchTimeline,
|
|
7
|
-
type BranchVersionNode,
|
|
8
|
-
type SessionBranchVersionEventEnvelope,
|
|
9
|
-
} from "./types.ts";
|
|
10
|
-
|
|
11
|
-
export type BranchSnapshot = SessionPersistenceSnapshot & {
|
|
12
|
-
inheritedEventCount: number;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export type OwnEventsReader = (
|
|
16
|
-
id: SessionId,
|
|
17
|
-
fromSeq: number,
|
|
18
|
-
signal?: AbortSignal,
|
|
19
|
-
) => Promise<readonly import("@deepseek-ai/dsh-session").SessionEvent[]>;
|
|
20
|
-
|
|
21
|
-
export async function buildTimeline(
|
|
22
|
-
snapshots: readonly BranchSnapshot[],
|
|
23
|
-
readOwnEvents: OwnEventsReader,
|
|
24
|
-
sessionId: SessionId,
|
|
25
|
-
signal?: AbortSignal,
|
|
26
|
-
): Promise<BranchTimeline> {
|
|
27
|
-
const byId = new Map(snapshots.map((snapshot) => [snapshot.header.id, snapshot] as const));
|
|
28
|
-
|
|
29
|
-
const ancestors: SessionId[] = [];
|
|
30
|
-
let cursor: SessionId | undefined = sessionId;
|
|
31
|
-
const seen = new Set<SessionId>();
|
|
32
|
-
while (cursor !== undefined) {
|
|
33
|
-
if (seen.has(cursor))
|
|
34
|
-
throw new SessionBranchError("lineage contains a cycle", "INVALID_BOUNDARY");
|
|
35
|
-
seen.add(cursor);
|
|
36
|
-
ancestors.push(cursor);
|
|
37
|
-
const snapshot = byId.get(cursor);
|
|
38
|
-
cursor = snapshot?.header.parentSession;
|
|
39
|
-
}
|
|
40
|
-
const rootId = ancestors.at(-1);
|
|
41
|
-
if (rootId === undefined) {
|
|
42
|
-
throw new SessionBranchError(`session "${sessionId}" is not persisted`, "SESSION_NOT_FOUND");
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const ordered: SessionId[] = [];
|
|
46
|
-
const queue: SessionId[] = [rootId];
|
|
47
|
-
while (queue.length > 0) {
|
|
48
|
-
const id = queue.shift();
|
|
49
|
-
if (id === undefined) continue;
|
|
50
|
-
ordered.push(id);
|
|
51
|
-
const children = snapshots
|
|
52
|
-
.filter((snapshot) => snapshot.header.parentSession === id)
|
|
53
|
-
.sort(
|
|
54
|
-
(left, right) =>
|
|
55
|
-
left.header.createdAt - right.header.createdAt ||
|
|
56
|
-
String(left.header.id).localeCompare(String(right.header.id)),
|
|
57
|
-
)
|
|
58
|
-
.map((snapshot) => snapshot.header.id);
|
|
59
|
-
queue.push(...children);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const nodes: BranchVersionNode[] = [];
|
|
63
|
-
const effectIds = new Set<string>();
|
|
64
|
-
for (const id of ordered) {
|
|
65
|
-
const snapshot = byId.get(id);
|
|
66
|
-
if (snapshot === undefined) continue;
|
|
67
|
-
const header = snapshot.header;
|
|
68
|
-
const node: BranchVersionNode = {
|
|
69
|
-
sessionId: header.id,
|
|
70
|
-
...(header.parentSession === undefined ? {} : { parentSessionId: header.parentSession }),
|
|
71
|
-
seedLength: snapshot.inheritedEventCount ?? 0,
|
|
72
|
-
createdAt: header.createdAt,
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
if (header.parentSession !== undefined) {
|
|
76
|
-
const events = await readOwnEvents(header.id, node.seedLength, signal);
|
|
77
|
-
|
|
78
|
-
let version: SessionBranchVersionEventEnvelope | undefined;
|
|
79
|
-
for (const event of events) {
|
|
80
|
-
if (isSessionBranchVersionEvent(event)) {
|
|
81
|
-
if (version !== undefined) {
|
|
82
|
-
throw new SessionBranchError(
|
|
83
|
-
`session ${header.id} carries multiple own version effects`,
|
|
84
|
-
"INVALID_BOUNDARY",
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
version = event;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
if (version !== undefined) {
|
|
91
|
-
const data = version.data;
|
|
92
|
-
if (
|
|
93
|
-
data.inverse.kind !== "restore-version" ||
|
|
94
|
-
data.inverse.sessionId !== header.parentSession
|
|
95
|
-
) {
|
|
96
|
-
throw new SessionBranchError(
|
|
97
|
-
`session ${header.id} version inverse does not match its parent`,
|
|
98
|
-
"INVALID_BOUNDARY",
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
|
-
if (effectIds.has(data.effect.id)) {
|
|
102
|
-
throw new SessionBranchError(
|
|
103
|
-
`version effect ${data.effect.id} is duplicated`,
|
|
104
|
-
"INVALID_BOUNDARY",
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
|
-
effectIds.add(data.effect.id);
|
|
108
|
-
node.effect = data.effect;
|
|
109
|
-
node.inverseSessionId = data.inverse.sessionId;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
nodes.push(node);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const root = nodes.find((node) => node.parentSessionId === undefined);
|
|
116
|
-
if (root === undefined) {
|
|
117
|
-
throw new SessionBranchError(`session "${sessionId}" lineage has no root`, "SESSION_NOT_FOUND");
|
|
118
|
-
}
|
|
119
|
-
return { root, nodes };
|
|
120
|
-
}
|