@khoralabs/chat 0.1.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.
- package/README.md +7 -0
- package/dist/domain.d.ts +10 -0
- package/dist/domain.d.ts.map +1 -0
- package/dist/errors.d.ts +20 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/events.d.ts +59 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/hash.d.ts +47 -0
- package/dist/hash.d.ts.map +1 -0
- package/dist/http/client.d.ts +21 -0
- package/dist/http/client.d.ts.map +1 -0
- package/dist/http/client.js +367 -0
- package/dist/http/config.d.ts +5 -0
- package/dist/http/config.d.ts.map +1 -0
- package/dist/http/index.d.ts +6 -0
- package/dist/http/index.d.ts.map +1 -0
- package/dist/http/index.js +1084 -0
- package/dist/http/routes.d.ts +15 -0
- package/dist/http/routes.d.ts.map +1 -0
- package/dist/http/routes.js +645 -0
- package/dist/http/server.d.ts +19 -0
- package/dist/http/server.d.ts.map +1 -0
- package/dist/http/server.js +973 -0
- package/dist/http/service.d.ts +34 -0
- package/dist/http/service.d.ts.map +1 -0
- package/dist/http/service.js +501 -0
- package/dist/ids.d.ts +8 -0
- package/dist/ids.d.ts.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +453 -0
- package/dist/lineage.d.ts +5 -0
- package/dist/lineage.d.ts.map +1 -0
- package/dist/persistence/core/index.d.ts +2 -0
- package/dist/persistence/core/index.d.ts.map +1 -0
- package/dist/persistence/core/persistence/base-persistence.d.ts +12 -0
- package/dist/persistence/core/persistence/base-persistence.d.ts.map +1 -0
- package/dist/persistence/core/persistence/helpers.d.ts +30 -0
- package/dist/persistence/core/persistence/helpers.d.ts.map +1 -0
- package/dist/persistence/core/persistence/index.d.ts +6 -0
- package/dist/persistence/core/persistence/index.d.ts.map +1 -0
- package/dist/persistence/core/persistence/index.js +1276 -0
- package/dist/persistence/core/persistence/memory-persistence.d.ts +55 -0
- package/dist/persistence/core/persistence/memory-persistence.d.ts.map +1 -0
- package/dist/persistence/core/persistence/signed-persistence.d.ts +18 -0
- package/dist/persistence/core/persistence/signed-persistence.d.ts.map +1 -0
- package/dist/persistence/core/persistence/types.d.ts +210 -0
- package/dist/persistence/core/persistence/types.d.ts.map +1 -0
- package/dist/persistence/sqlite/index.d.ts +3 -0
- package/dist/persistence/sqlite/index.d.ts.map +1 -0
- package/dist/persistence/sqlite/index.js +1544 -0
- package/dist/persistence/sqlite/persistence/persistence.d.ts +49 -0
- package/dist/persistence/sqlite/persistence/persistence.d.ts.map +1 -0
- package/dist/persistence/sqlite/persistence/schema.d.ts +4 -0
- package/dist/persistence/sqlite/persistence/schema.d.ts.map +1 -0
- package/dist/persistence/sqlite/persistence/streaming.d.ts +27 -0
- package/dist/persistence/sqlite/persistence/streaming.d.ts.map +1 -0
- package/dist/persistence/testing/index.d.ts +2 -0
- package/dist/persistence/testing/index.d.ts.map +1 -0
- package/dist/persistence/testing/index.js +689 -0
- package/dist/persistence/testing/persistence/contract.d.ts +3 -0
- package/dist/persistence/testing/persistence/contract.d.ts.map +1 -0
- package/dist/persistence/turso-serverless/index.d.ts +10 -0
- package/dist/persistence/turso-serverless/index.d.ts.map +1 -0
- package/dist/persistence/turso-serverless/index.js +1812 -0
- package/dist/persistence/turso-serverless/persistence/local-sqlite.d.ts +5 -0
- package/dist/persistence/turso-serverless/persistence/local-sqlite.d.ts.map +1 -0
- package/dist/persistence/turso-serverless/persistence/persistence.d.ts +49 -0
- package/dist/persistence/turso-serverless/persistence/persistence.d.ts.map +1 -0
- package/dist/persistence/turso-serverless/persistence/schema.d.ts +4 -0
- package/dist/persistence/turso-serverless/persistence/schema.d.ts.map +1 -0
- package/dist/persistence/turso-serverless/persistence/sql.d.ts +20 -0
- package/dist/persistence/turso-serverless/persistence/sql.d.ts.map +1 -0
- package/dist/persistence/turso-serverless/persistence/sql.js +53 -0
- package/dist/persistence/turso-serverless/persistence/streaming.d.ts +27 -0
- package/dist/persistence/turso-serverless/persistence/streaming.d.ts.map +1 -0
- package/dist/service.d.ts +49 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/stream.d.ts +32 -0
- package/dist/stream.d.ts.map +1 -0
- package/dist/types.d.ts +201 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/validation.d.ts +4 -0
- package/dist/validation.d.ts.map +1 -0
- package/package.json +104 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
3
|
+
|
|
4
|
+
// src/errors.ts
|
|
5
|
+
class ChatError extends Error {
|
|
6
|
+
code;
|
|
7
|
+
constructor(code, message) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = "ChatError";
|
|
10
|
+
this.code = code;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
class ChatNotFoundError extends ChatError {
|
|
15
|
+
resource;
|
|
16
|
+
resourceId;
|
|
17
|
+
constructor(resource, resourceId) {
|
|
18
|
+
super("not_found", `${resource} not found: ${resourceId}`);
|
|
19
|
+
this.name = "ChatNotFoundError";
|
|
20
|
+
this.resource = resource;
|
|
21
|
+
this.resourceId = resourceId;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
class ChatConflictError extends ChatError {
|
|
26
|
+
reason;
|
|
27
|
+
constructor(reason, message) {
|
|
28
|
+
super("conflict", message);
|
|
29
|
+
this.name = "ChatConflictError";
|
|
30
|
+
this.reason = reason;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
class ChatValidationError extends ChatError {
|
|
35
|
+
constructor(message) {
|
|
36
|
+
super("validation", message);
|
|
37
|
+
this.name = "ChatValidationError";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
class ChatPermissionError extends ChatError {
|
|
42
|
+
constructor(message) {
|
|
43
|
+
super("permission_denied", message);
|
|
44
|
+
this.name = "ChatPermissionError";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// src/hash.ts
|
|
48
|
+
function sha256Hex(input) {
|
|
49
|
+
const hasher = new Bun.CryptoHasher("sha256");
|
|
50
|
+
hasher.update(input);
|
|
51
|
+
return hasher.digest("hex");
|
|
52
|
+
}
|
|
53
|
+
function stableStringify(value) {
|
|
54
|
+
if (value === undefined) {
|
|
55
|
+
return "null";
|
|
56
|
+
}
|
|
57
|
+
if (value === null || typeof value !== "object") {
|
|
58
|
+
return JSON.stringify(value);
|
|
59
|
+
}
|
|
60
|
+
if (Array.isArray(value)) {
|
|
61
|
+
return `[${value.map((item) => stableStringify(item)).join(",")}]`;
|
|
62
|
+
}
|
|
63
|
+
const record = value;
|
|
64
|
+
const keys = Object.keys(record).sort();
|
|
65
|
+
return `{${keys.map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`).join(",")}}`;
|
|
66
|
+
}
|
|
67
|
+
function canonicalPostVersionPayload(input) {
|
|
68
|
+
return stableStringify({
|
|
69
|
+
postId: input.postId,
|
|
70
|
+
versionId: input.versionId,
|
|
71
|
+
threadId: input.threadId,
|
|
72
|
+
author: input.author,
|
|
73
|
+
role: input.role,
|
|
74
|
+
parts: input.parts,
|
|
75
|
+
metadata: input.metadata ?? null,
|
|
76
|
+
mentions: input.mentions ?? null,
|
|
77
|
+
model: input.model ?? null,
|
|
78
|
+
usage: input.usage ?? null,
|
|
79
|
+
parentVersionId: input.parentVersionId ?? null,
|
|
80
|
+
previousPostVersionId: input.previousPostVersionId ?? null
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
function computeContentHash(payload) {
|
|
84
|
+
return sha256Hex(canonicalPostVersionPayload(payload));
|
|
85
|
+
}
|
|
86
|
+
function computeLineageHash(input) {
|
|
87
|
+
return sha256Hex(stableStringify({
|
|
88
|
+
previousLineageHash: input.previousLineageHash,
|
|
89
|
+
contentHash: input.contentHash,
|
|
90
|
+
postId: input.postId,
|
|
91
|
+
versionId: input.versionId
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
94
|
+
function canonicalAclEventPayload(input) {
|
|
95
|
+
return stableStringify({
|
|
96
|
+
type: input.type,
|
|
97
|
+
targetType: input.targetType,
|
|
98
|
+
targetId: input.targetId,
|
|
99
|
+
scope: input.scope ?? null,
|
|
100
|
+
role: input.role ?? null,
|
|
101
|
+
actor: input.actor,
|
|
102
|
+
previousAclEventId: input.previousAclEventId ?? null
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
function computeAclEventContentHash(payload) {
|
|
106
|
+
return sha256Hex(canonicalAclEventPayload(payload));
|
|
107
|
+
}
|
|
108
|
+
function signedPayloadBytes(payload) {
|
|
109
|
+
return new TextEncoder().encode(payload);
|
|
110
|
+
}
|
|
111
|
+
function canonicalSignedPostVersionPayload(input) {
|
|
112
|
+
return stableStringify({
|
|
113
|
+
contentHash: input.contentHash,
|
|
114
|
+
lineageHash: input.lineageHash,
|
|
115
|
+
payload: JSON.parse(canonicalPostVersionPayload(input))
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
function canonicalSignedAclEventPayload(input) {
|
|
119
|
+
return stableStringify({
|
|
120
|
+
contentHash: input.contentHash,
|
|
121
|
+
payload: JSON.parse(canonicalAclEventPayload(input.payload))
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
async function maybeSignPayload(payload, signer, sign) {
|
|
125
|
+
return sign(signedPayloadBytes(payload), signer);
|
|
126
|
+
}
|
|
127
|
+
// src/ids.ts
|
|
128
|
+
function createId() {
|
|
129
|
+
return crypto.randomUUID();
|
|
130
|
+
}
|
|
131
|
+
function scopeKey(scope) {
|
|
132
|
+
return `${scope.type}:${scope.id}`;
|
|
133
|
+
}
|
|
134
|
+
function scopeRefFromKey(key) {
|
|
135
|
+
const separatorIndex = key.indexOf(":");
|
|
136
|
+
if (separatorIndex === -1) {
|
|
137
|
+
throw new Error(`Invalid scope key: ${key}`);
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
type: key.slice(0, separatorIndex),
|
|
141
|
+
id: key.slice(separatorIndex + 1)
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
// src/lineage.ts
|
|
145
|
+
function walkLineageFromHead(headVersionId, versionsById) {
|
|
146
|
+
const lineage = [];
|
|
147
|
+
let currentId = headVersionId;
|
|
148
|
+
const seenPostIds = new Set;
|
|
149
|
+
while (currentId) {
|
|
150
|
+
const version = versionsById.get(currentId);
|
|
151
|
+
if (!version)
|
|
152
|
+
break;
|
|
153
|
+
if (!seenPostIds.has(version.postId)) {
|
|
154
|
+
lineage.push(version);
|
|
155
|
+
seenPostIds.add(version.postId);
|
|
156
|
+
}
|
|
157
|
+
currentId = version.previousPostVersionId;
|
|
158
|
+
}
|
|
159
|
+
return lineage.reverse();
|
|
160
|
+
}
|
|
161
|
+
function lineageBetween(headVersionId, ancestorVersionId, versionsById) {
|
|
162
|
+
const lineage = walkLineageFromHead(headVersionId, versionsById);
|
|
163
|
+
const startIndex = lineage.findIndex((version) => version.id === ancestorVersionId);
|
|
164
|
+
if (startIndex === -1)
|
|
165
|
+
return null;
|
|
166
|
+
return lineage.slice(startIndex);
|
|
167
|
+
}
|
|
168
|
+
function postFromVersion(version, index, deletedAtMs) {
|
|
169
|
+
return {
|
|
170
|
+
...version,
|
|
171
|
+
id: version.postId,
|
|
172
|
+
status: "complete",
|
|
173
|
+
versionId: version.id,
|
|
174
|
+
previousVersionId: version.parentVersionId ?? null,
|
|
175
|
+
index,
|
|
176
|
+
updatedAtMs: version.parentVersionId ? version.createdAtMs : null,
|
|
177
|
+
deletedAtMs: deletedAtMs ?? null
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
// src/stream.ts
|
|
181
|
+
function rebuildStreamCacheFromEvents(events) {
|
|
182
|
+
const sorted = [...events].sort((a, b) => a.revision - b.revision);
|
|
183
|
+
let message = null;
|
|
184
|
+
let mentions;
|
|
185
|
+
let model;
|
|
186
|
+
let usage;
|
|
187
|
+
let revision = 0;
|
|
188
|
+
for (const event of sorted) {
|
|
189
|
+
if (event.eventType === "stream.started") {
|
|
190
|
+
if (!event.message) {
|
|
191
|
+
throw new Error(`stream.started event ${event.id} missing message`);
|
|
192
|
+
}
|
|
193
|
+
message = event.message;
|
|
194
|
+
mentions = event.mentions;
|
|
195
|
+
model = event.model;
|
|
196
|
+
usage = event.usage;
|
|
197
|
+
revision = event.revision;
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
if (event.eventType === "stream.delta") {
|
|
201
|
+
if (!event.message) {
|
|
202
|
+
throw new Error(`stream.delta event ${event.id} missing message`);
|
|
203
|
+
}
|
|
204
|
+
message = event.message;
|
|
205
|
+
if (event.mentions !== undefined)
|
|
206
|
+
mentions = event.mentions;
|
|
207
|
+
if (event.model !== undefined)
|
|
208
|
+
model = event.model;
|
|
209
|
+
if (event.usage !== undefined)
|
|
210
|
+
usage = event.usage;
|
|
211
|
+
revision = event.revision;
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
if (event.eventType === "stream.completed" || event.eventType === "stream.aborted") {
|
|
215
|
+
revision = event.revision;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (!message) {
|
|
219
|
+
throw new Error("stream events did not produce a message");
|
|
220
|
+
}
|
|
221
|
+
return { message, mentions, model, usage, revision };
|
|
222
|
+
}
|
|
223
|
+
function streamingPostFromCache(input) {
|
|
224
|
+
return {
|
|
225
|
+
...input.message,
|
|
226
|
+
id: input.postId,
|
|
227
|
+
status: "streaming",
|
|
228
|
+
threadId: input.threadId,
|
|
229
|
+
author: input.author,
|
|
230
|
+
mentions: input.mentions,
|
|
231
|
+
model: input.model,
|
|
232
|
+
usage: input.usage,
|
|
233
|
+
index: input.index,
|
|
234
|
+
streamRevision: input.streamRevision,
|
|
235
|
+
createdAtMs: input.createdAtMs,
|
|
236
|
+
updatedAtMs: input.updatedAtMs ?? null,
|
|
237
|
+
deletedAtMs: input.deletedAtMs ?? null
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
function mergeThreadPostsForList(lineagePosts, activePosts) {
|
|
241
|
+
const byIndex = new Map;
|
|
242
|
+
for (const item of lineagePosts) {
|
|
243
|
+
byIndex.set(item.index, item.post);
|
|
244
|
+
}
|
|
245
|
+
for (const post of activePosts) {
|
|
246
|
+
if (post.status === "streaming" || post.status === "aborted") {
|
|
247
|
+
byIndex.set(post.index, post);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return [...byIndex.entries()].sort(([a], [b]) => a - b).map(([, post]) => post);
|
|
251
|
+
}
|
|
252
|
+
// src/validation.ts
|
|
253
|
+
function assertThreadRoot(root) {
|
|
254
|
+
if (root.type === "channel") {
|
|
255
|
+
if (!root.channelId.trim()) {
|
|
256
|
+
throw new ChatValidationError("channel root requires channelId");
|
|
257
|
+
}
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
if (!root.postId.trim()) {
|
|
261
|
+
throw new ChatValidationError("post root requires postId");
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
function assertNonEmptyString(value, label) {
|
|
265
|
+
if (!value.trim()) {
|
|
266
|
+
throw new ChatValidationError(`${label} is required`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
// src/service.ts
|
|
270
|
+
function createChatService(persistence, options = {}) {
|
|
271
|
+
const emit = (event) => {
|
|
272
|
+
options.onEvent?.(event);
|
|
273
|
+
};
|
|
274
|
+
async function requireChannel(id) {
|
|
275
|
+
const channel = await persistence.getChannel(id);
|
|
276
|
+
if (!channel)
|
|
277
|
+
throw new ChatNotFoundError("channel", id);
|
|
278
|
+
return channel;
|
|
279
|
+
}
|
|
280
|
+
async function requireThread(id) {
|
|
281
|
+
const thread = await persistence.getThread(id);
|
|
282
|
+
if (!thread)
|
|
283
|
+
throw new ChatNotFoundError("thread", id);
|
|
284
|
+
return thread;
|
|
285
|
+
}
|
|
286
|
+
async function requirePost(id) {
|
|
287
|
+
const post = await persistence.getPost(id);
|
|
288
|
+
if (!post)
|
|
289
|
+
throw new ChatNotFoundError("post", id);
|
|
290
|
+
return post;
|
|
291
|
+
}
|
|
292
|
+
return {
|
|
293
|
+
async createChannel(input) {
|
|
294
|
+
const channel = await persistence.createChannel({
|
|
295
|
+
...input,
|
|
296
|
+
id: input.id ?? options.generateId?.() ?? createId()
|
|
297
|
+
});
|
|
298
|
+
emit({ type: "channel.created", channel });
|
|
299
|
+
return channel;
|
|
300
|
+
},
|
|
301
|
+
async createThread(input) {
|
|
302
|
+
const thread = await persistence.createThread({
|
|
303
|
+
...input,
|
|
304
|
+
id: input.id ?? options.generateId?.() ?? createId()
|
|
305
|
+
});
|
|
306
|
+
emit({ type: "thread.created", thread });
|
|
307
|
+
return thread;
|
|
308
|
+
},
|
|
309
|
+
getChannel: requireChannel,
|
|
310
|
+
getThread: requireThread,
|
|
311
|
+
getPost: requirePost,
|
|
312
|
+
listThreads: (input) => persistence.listThreads(input),
|
|
313
|
+
listPosts: (input) => persistence.listPosts(input),
|
|
314
|
+
getThreadTip: (threadId) => persistence.getThreadTip(threadId),
|
|
315
|
+
async appendPost(input) {
|
|
316
|
+
const result = await persistence.appendPost(input);
|
|
317
|
+
if (!result.ok) {
|
|
318
|
+
throw new ChatConflictError("head_conflict", `head conflict: expected ${input.expectedHeadPostVersionId ?? "null"}, current ${result.currentHead.headPostVersionId}`);
|
|
319
|
+
}
|
|
320
|
+
emit({
|
|
321
|
+
type: "post.appended",
|
|
322
|
+
threadId: input.threadId,
|
|
323
|
+
post: result.post
|
|
324
|
+
});
|
|
325
|
+
return { post: result.post, head: result.head };
|
|
326
|
+
},
|
|
327
|
+
async editPost(input) {
|
|
328
|
+
const result = await persistence.editPost(input);
|
|
329
|
+
if (!result.ok) {
|
|
330
|
+
throw new ChatConflictError("head_conflict", `head conflict during edit: current ${result.currentHead.headPostVersionId}`);
|
|
331
|
+
}
|
|
332
|
+
emit({
|
|
333
|
+
type: "post.updated",
|
|
334
|
+
threadId: result.post.threadId,
|
|
335
|
+
post: result.post
|
|
336
|
+
});
|
|
337
|
+
return { post: result.post, head: result.head };
|
|
338
|
+
},
|
|
339
|
+
async deletePost(input) {
|
|
340
|
+
const post = await persistence.deletePost(input);
|
|
341
|
+
emit({
|
|
342
|
+
type: "post.deleted",
|
|
343
|
+
threadId: post.threadId,
|
|
344
|
+
postId: post.id,
|
|
345
|
+
deletedAtMs: post.deletedAtMs ?? Date.now()
|
|
346
|
+
});
|
|
347
|
+
return post;
|
|
348
|
+
},
|
|
349
|
+
addChannelMember: (input) => persistence.addChannelMember(input),
|
|
350
|
+
removeChannelMember: (input) => persistence.removeChannelMember(input),
|
|
351
|
+
async addThreadParticipant(input) {
|
|
352
|
+
const event = await persistence.addThreadParticipant(input);
|
|
353
|
+
emit({
|
|
354
|
+
type: "participant.added",
|
|
355
|
+
threadId: input.threadId,
|
|
356
|
+
scope: input.scope,
|
|
357
|
+
role: input.role
|
|
358
|
+
});
|
|
359
|
+
return event;
|
|
360
|
+
},
|
|
361
|
+
removeThreadParticipant: (input) => persistence.removeThreadParticipant(input),
|
|
362
|
+
createThreadHead: (input) => persistence.createThreadHead(input),
|
|
363
|
+
listChannelMembers: (channelId) => persistence.listChannelMembers(channelId),
|
|
364
|
+
listThreadParticipants: (threadId) => persistence.listThreadParticipants(threadId),
|
|
365
|
+
async startStreamedPost(input) {
|
|
366
|
+
const result = await persistence.startStreamedPost(input);
|
|
367
|
+
emit({
|
|
368
|
+
type: "post.stream.started",
|
|
369
|
+
threadId: input.threadId,
|
|
370
|
+
post: result.post,
|
|
371
|
+
revision: result.revision
|
|
372
|
+
});
|
|
373
|
+
return result;
|
|
374
|
+
},
|
|
375
|
+
async applyPostDelta(input) {
|
|
376
|
+
const record = await persistence.getPost(input.postId);
|
|
377
|
+
if (!record)
|
|
378
|
+
throw new ChatNotFoundError("post", input.postId);
|
|
379
|
+
const result = await persistence.applyPostDelta(input);
|
|
380
|
+
emit({
|
|
381
|
+
type: "post.stream.delta",
|
|
382
|
+
threadId: record.threadId,
|
|
383
|
+
post: result.post,
|
|
384
|
+
revision: result.revision
|
|
385
|
+
});
|
|
386
|
+
return result;
|
|
387
|
+
},
|
|
388
|
+
async completeStreamedPost(input) {
|
|
389
|
+
const record = await persistence.getPost(input.postId);
|
|
390
|
+
if (!record)
|
|
391
|
+
throw new ChatNotFoundError("post", input.postId);
|
|
392
|
+
const streamRevision = record.status === "streaming" ? record.streamRevision : input.expectedRevision;
|
|
393
|
+
const result = await persistence.completeStreamedPost(input);
|
|
394
|
+
if (!result.ok) {
|
|
395
|
+
throw new ChatConflictError("head_conflict", `head conflict during stream completion: current ${result.currentHead.headPostVersionId}`);
|
|
396
|
+
}
|
|
397
|
+
emit({
|
|
398
|
+
type: "post.stream.completed",
|
|
399
|
+
threadId: record.threadId,
|
|
400
|
+
post: result.post,
|
|
401
|
+
head: result.head,
|
|
402
|
+
revision: typeof streamRevision === "number" ? streamRevision + 1 : 0
|
|
403
|
+
});
|
|
404
|
+
return { post: result.post, head: result.head };
|
|
405
|
+
},
|
|
406
|
+
async abortStreamedPost(input) {
|
|
407
|
+
const record = await persistence.getPost(input.postId);
|
|
408
|
+
if (!record)
|
|
409
|
+
throw new ChatNotFoundError("post", input.postId);
|
|
410
|
+
const result = await persistence.abortStreamedPost(input);
|
|
411
|
+
emit({
|
|
412
|
+
type: "post.stream.aborted",
|
|
413
|
+
threadId: record.threadId,
|
|
414
|
+
postId: input.postId,
|
|
415
|
+
revision: result.post.streamRevision,
|
|
416
|
+
deletedAtMs: result.post.deletedAtMs ?? Date.now()
|
|
417
|
+
});
|
|
418
|
+
return result.post;
|
|
419
|
+
},
|
|
420
|
+
setPostVersionSignature(versionId, signature) {
|
|
421
|
+
return persistence.setPostVersionSignature(versionId, signature);
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
export {
|
|
426
|
+
walkLineageFromHead,
|
|
427
|
+
streamingPostFromCache,
|
|
428
|
+
signedPayloadBytes,
|
|
429
|
+
sha256Hex,
|
|
430
|
+
scopeRefFromKey,
|
|
431
|
+
scopeKey,
|
|
432
|
+
rebuildStreamCacheFromEvents,
|
|
433
|
+
postFromVersion,
|
|
434
|
+
mergeThreadPostsForList,
|
|
435
|
+
maybeSignPayload,
|
|
436
|
+
lineageBetween,
|
|
437
|
+
createId,
|
|
438
|
+
createChatService,
|
|
439
|
+
computeLineageHash,
|
|
440
|
+
computeContentHash,
|
|
441
|
+
computeAclEventContentHash,
|
|
442
|
+
canonicalSignedPostVersionPayload,
|
|
443
|
+
canonicalSignedAclEventPayload,
|
|
444
|
+
canonicalPostVersionPayload,
|
|
445
|
+
canonicalAclEventPayload,
|
|
446
|
+
assertThreadRoot,
|
|
447
|
+
assertNonEmptyString,
|
|
448
|
+
ChatValidationError,
|
|
449
|
+
ChatPermissionError,
|
|
450
|
+
ChatNotFoundError,
|
|
451
|
+
ChatError,
|
|
452
|
+
ChatConflictError
|
|
453
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { CommittedPost, PostVersion } from "./types.js";
|
|
2
|
+
export declare function walkLineageFromHead<T extends Pick<PostVersion, "id" | "postId" | "previousPostVersionId">>(headVersionId: string, versionsById: Map<string, T>): T[];
|
|
3
|
+
export declare function lineageBetween(headVersionId: string, ancestorVersionId: string, versionsById: Map<string, Pick<PostVersion, "id" | "postId" | "previousPostVersionId">>): Pick<PostVersion, "id" | "postId" | "previousPostVersionId">[] | null;
|
|
4
|
+
export declare function postFromVersion(version: PostVersion, index: number, deletedAtMs?: number | null): CommittedPost;
|
|
5
|
+
//# sourceMappingURL=lineage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lineage.d.ts","sourceRoot":"","sources":["../src/lineage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE7D,wBAAgB,mBAAmB,CACjC,CAAC,SAAS,IAAI,CAAC,WAAW,EAAE,IAAI,GAAG,QAAQ,GAAG,uBAAuB,CAAC,EACtE,aAAa,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAgB1D;AAED,wBAAgB,cAAc,CAC5B,aAAa,EAAE,MAAM,EACrB,iBAAiB,EAAE,MAAM,EACzB,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,GAAG,QAAQ,GAAG,uBAAuB,CAAC,CAAC,GACtF,IAAI,CAAC,WAAW,EAAE,IAAI,GAAG,QAAQ,GAAG,uBAAuB,CAAC,EAAE,GAAG,IAAI,CAKvE;AAED,wBAAgB,eAAe,CAC7B,OAAO,EAAE,WAAW,EACpB,KAAK,EAAE,MAAM,EACb,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,GAC1B,aAAa,CAWf"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/persistence/core/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { CreateThreadInput, ThreadRoot } from "../../../domain.js";
|
|
2
|
+
export declare abstract class BaseChatPersistence {
|
|
3
|
+
protected assertThreadRootExists(root: ThreadRoot): void;
|
|
4
|
+
protected requireChannel(id: string): Promise<import("../../../types.js").Channel>;
|
|
5
|
+
protected requireThread(id: string): Promise<import("../../../types.js").Thread>;
|
|
6
|
+
protected requirePost(id: string): Promise<import("../../../types.js").Post>;
|
|
7
|
+
protected validateCreateThreadInput(input: CreateThreadInput): void;
|
|
8
|
+
abstract getChannel(id: string): Promise<import("../../../domain.js").Channel | null>;
|
|
9
|
+
abstract getThread(id: string): Promise<import("../../../domain.js").Thread | null>;
|
|
10
|
+
abstract getPost(id: string): Promise<import("../../../domain.js").Post | null>;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=base-persistence.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base-persistence.d.ts","sourceRoot":"","sources":["../../../../src/persistence/core/persistence/base-persistence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAGxE,8BAAsB,mBAAmB;IACvC,SAAS,CAAC,sBAAsB,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI;cASxC,cAAc,CAAC,EAAE,EAAE,MAAM;cAMzB,aAAa,CAAC,EAAE,EAAE,MAAM;cAMxB,WAAW,CAAC,EAAE,EAAE,MAAM;IAMtC,SAAS,CAAC,yBAAyB,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI;IAInE,QAAQ,CAAC,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,oBAAoB,EAAE,OAAO,GAAG,IAAI,CAAC;IACrF,QAAQ,CAAC,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnF,QAAQ,CAAC,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,oBAAoB,EAAE,IAAI,GAAG,IAAI,CAAC;CAChF"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { AppendPostInput, EditPostInput, PreparedAppendPost, PreparedEditPost } from "../../../domain.js";
|
|
2
|
+
export type PrepareAppendOptions = {
|
|
3
|
+
generateId?: () => string;
|
|
4
|
+
now?: () => number;
|
|
5
|
+
};
|
|
6
|
+
export declare function prepareAppendPost(input: AppendPostInput & {
|
|
7
|
+
previousPostVersionId: string | null;
|
|
8
|
+
previousLineageHash: string | null;
|
|
9
|
+
}, options?: PrepareAppendOptions): PreparedAppendPost;
|
|
10
|
+
export declare function prepareEditPost(input: EditPostInput & {
|
|
11
|
+
threadId: string;
|
|
12
|
+
previousPostVersionId: string | null;
|
|
13
|
+
previousLineageHash: string | null;
|
|
14
|
+
}, options?: PrepareAppendOptions): PreparedEditPost;
|
|
15
|
+
export declare function buildAclEventContentHash(input: {
|
|
16
|
+
type: string;
|
|
17
|
+
targetType: "channel" | "thread";
|
|
18
|
+
targetId: string;
|
|
19
|
+
scope?: {
|
|
20
|
+
type: string;
|
|
21
|
+
id: string;
|
|
22
|
+
};
|
|
23
|
+
role?: string;
|
|
24
|
+
actor: {
|
|
25
|
+
type: string;
|
|
26
|
+
id: string;
|
|
27
|
+
};
|
|
28
|
+
previousAclEventId?: string | null;
|
|
29
|
+
}): string;
|
|
30
|
+
//# sourceMappingURL=helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../../src/persistence/core/persistence/helpers.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,eAAe,EACf,aAAa,EACb,kBAAkB,EAClB,gBAAgB,EACjB,MAAM,oBAAoB,CAAC;AAQ5B,MAAM,MAAM,oBAAoB,GAAG;IACjC,UAAU,CAAC,EAAE,MAAM,MAAM,CAAC;IAC1B,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB,CAAC;AAEF,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,eAAe,GAAG;IACvB,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC,EACD,OAAO,GAAE,oBAAyB,GACjC,kBAAkB,CA8CpB;AAED,wBAAgB,eAAe,CAC7B,KAAK,EAAE,aAAa,GAAG;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC,EACD,OAAO,GAAE,oBAAyB,GACjC,gBAAgB,CA8ClB;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,SAAS,GAAG,QAAQ,CAAC;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IACpC,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC,GAAG,MAAM,CAET"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export * from "./base-persistence.js";
|
|
2
|
+
export * from "./helpers.js";
|
|
3
|
+
export * from "./memory-persistence.js";
|
|
4
|
+
export { prepareAppendForSigning, type SignedChatPersistenceOptions, signPreparedAppendPost, withSignedChatPersistence, } from "./signed-persistence.js";
|
|
5
|
+
export type { AbortStreamedPostInput, AbortStreamedPostResult, AddChannelMemberInput, AddThreadParticipantInput, AppendPostInput, AppendPostResult, ApplyPostDeltaInput, ApplyPostDeltaResult, ChatPersistence, ChatReadPersistence, ChatWritePersistence, CompleteStreamedPostInput, CompleteStreamedPostResult, CreateChannelInput, CreateThreadInput, DeletePostInput, EditPostInput, EditPostResult, ListPostsInput, ListThreadsInput, PreparedAppendPost, PreparedEditPost, RemoveChannelMemberInput, RemoveThreadParticipantInput, StartStreamedPostInput, StartStreamedPostResult, } from "./types.js";
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/persistence/core/persistence/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,cAAc,CAAC;AAC7B,cAAc,yBAAyB,CAAC;AACxC,OAAO,EACL,uBAAuB,EACvB,KAAK,4BAA4B,EACjC,sBAAsB,EACtB,yBAAyB,GAC1B,MAAM,yBAAyB,CAAC;AACjC,YAAY,EACV,sBAAsB,EACtB,uBAAuB,EACvB,qBAAqB,EACrB,yBAAyB,EACzB,eAAe,EACf,gBAAgB,EAChB,mBAAmB,EACnB,oBAAoB,EACpB,eAAe,EACf,mBAAmB,EACnB,oBAAoB,EACpB,yBAAyB,EACzB,0BAA0B,EAC1B,kBAAkB,EAClB,iBAAiB,EACjB,eAAe,EACf,aAAa,EACb,cAAc,EACd,cAAc,EACd,gBAAgB,EAChB,kBAAkB,EAClB,gBAAgB,EAChB,wBAAwB,EACxB,4BAA4B,EAC5B,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,YAAY,CAAC"}
|