@wirecat/cli-core 0.380.0 → 0.382.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/CHANGELOG.md +63 -1
- package/dist/cli/bot/watch.js +4 -1
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/messenger/chat-answers.d.ts +2 -0
- package/dist/cli/messenger/chat-answers.js +2 -0
- package/dist/cli/messenger/chat-lists.d.ts +2 -0
- package/dist/cli/messenger/context.d.ts +2 -2
- package/dist/cli/messenger/context.js +2 -1
- package/dist/cli/messenger/conversation-answers.d.ts +1 -0
- package/dist/cli/messenger/message-answers.d.ts +1 -0
- package/dist/cli/messenger/message-items.js +1 -0
- package/dist/cli/messenger/message-writes.d.ts +1 -0
- package/dist/cli/messenger/serve-relay.d.ts +1 -0
- package/dist/cli/messenger/stored.d.ts +3 -1
- package/dist/cli/messenger/stored.js +24 -12
- package/dist/cli/messenger/watch-command.js +8 -0
- package/dist/domain/models.d.ts +52 -4
- package/dist/entities/chat.d.ts +4 -0
- package/dist/entities/chat.js +4 -0
- package/dist/entities/contact.d.ts +4 -0
- package/dist/entities/evidence.d.ts +8 -0
- package/dist/entities/index.d.ts +17 -0
- package/dist/entities/message.d.ts +2 -0
- package/dist/entities/message.js +2 -0
- package/dist/errors/reasons.js +1 -0
- package/dist/meetings/answers.d.ts +1 -0
- package/dist/meetings/import.js +1 -0
- package/dist/meetings/index.d.ts +1 -1
- package/dist/meetings/index.js +1 -1
- package/dist/meetings/item.d.ts +10 -0
- package/dist/meetings/item.js +34 -18
- package/dist/meetings/store.d.ts +6 -0
- package/dist/ports/messenger.d.ts +10 -3
- package/dist/ports/traits.d.ts +6 -0
- package/dist/resolve.d.ts +6 -0
- package/dist/resolve.js +13 -4
- package/dist/search/garbled.d.ts +2 -1
- package/dist/search/garbled.js +6 -1
- package/dist/services/chats.js +16 -2
- package/dist/services/deps.d.ts +2 -0
- package/dist/services/deps.js +2 -0
- package/dist/services/inbox.d.ts +1 -0
- package/dist/services/inbox.js +10 -3
- package/dist/store/keys.d.ts +1 -1
- package/dist/store/messages.d.ts +22 -3
- package/dist/store/messages.js +22 -3
- package/dist/store/sqlite/accounts.js +2 -1
- package/dist/store/sqlite/chat-rekey.js +18 -2
- package/dist/store/sqlite/chats.d.ts +2 -2
- package/dist/store/sqlite/chats.js +39 -9
- package/dist/store/sqlite/manifest.js +3 -0
- package/dist/store/sqlite/meeting-hit-query.js +18 -3
- package/dist/store/sqlite/meetings.js +56 -1
- package/dist/store/sqlite/messages.d.ts +19 -4
- package/dist/store/sqlite/messages.js +103 -5
- package/dist/store/sqlite/migrations.generated.js +23 -0
- package/dist/store/sqlite/open-store.js +16 -5
- package/dist/store/sqlite/reads.d.ts +50 -0
- package/dist/store/sqlite/reads.js +3 -1
- package/dist/store/sqlite/schema.d.ts +251 -0
- package/dist/store/sqlite/schema.js +28 -0
- package/dist/store/sqlite/search.d.ts +33 -0
- package/dist/store/sqlite/values.d.ts +4 -0
- package/dist/store/sqlite/values.js +3 -0
- package/package.json +1 -1
|
@@ -7,7 +7,7 @@ import { folded } from "./folded.js";
|
|
|
7
7
|
import { identityPk } from "./identities.js";
|
|
8
8
|
import { attachments, chats, messageRevisions, messages, messageTranscripts } from "./schema.js";
|
|
9
9
|
import { intoVoiceAttachment } from "./transcripts.js";
|
|
10
|
-
import { json, parsed, toMs } from "./values.js";
|
|
10
|
+
import { DELIVERY, json, parsed, toMs } from "./values.js";
|
|
11
11
|
import { purgeVectorsOf } from "./vectors.js";
|
|
12
12
|
const FIELDS = [
|
|
13
13
|
"threadExternalId",
|
|
@@ -120,7 +120,7 @@ export const upsertMessage = (context, key, accountKey, chatKey, message, via, s
|
|
|
120
120
|
const revived = found?.deletedAt != null && seenAt !== undefined && found.deletedAt < seenAt;
|
|
121
121
|
// A deleted message leaves no text behind, and a sync that still carries it does not bring it back.
|
|
122
122
|
if (found?.deletedAt != null && !revived)
|
|
123
|
-
return;
|
|
123
|
+
return false;
|
|
124
124
|
let pk;
|
|
125
125
|
if (!found) {
|
|
126
126
|
pk = Number(statements.insert.get({
|
|
@@ -174,6 +174,13 @@ export const upsertMessage = (context, key, accountKey, chatKey, message, via, s
|
|
|
174
174
|
if (revived)
|
|
175
175
|
orm.update(messages).set({ deletedAt: null, text: message.text }).where(eq(messages.id, pk)).run();
|
|
176
176
|
}
|
|
177
|
+
const derived = derivedReactions(context, chatKey, message.id);
|
|
178
|
+
if (derived)
|
|
179
|
+
orm
|
|
180
|
+
.update(messages)
|
|
181
|
+
.set({ reactions: JSON.stringify(derived) })
|
|
182
|
+
.where(eq(messages.id, pk))
|
|
183
|
+
.run();
|
|
177
184
|
if (message.counterObservations)
|
|
178
185
|
applyCounterObservations(context, pk, message.counterObservations);
|
|
179
186
|
if (message.starred !== undefined)
|
|
@@ -182,6 +189,12 @@ export const upsertMessage = (context, key, accountKey, chatKey, message, via, s
|
|
|
182
189
|
.set({ starred: message.starred ? 1 : 0 })
|
|
183
190
|
.where(eq(messages.id, pk))
|
|
184
191
|
.run();
|
|
192
|
+
if (message.delivery !== undefined)
|
|
193
|
+
orm
|
|
194
|
+
.update(messages)
|
|
195
|
+
.set({ delivery: raised(message.delivery) })
|
|
196
|
+
.where(and(eq(messages.id, pk), eq(messages.outgoing, 1)))
|
|
197
|
+
.run();
|
|
185
198
|
const root = message.threadRootId ?? message.threadId;
|
|
186
199
|
if (root !== undefined) {
|
|
187
200
|
context.database
|
|
@@ -215,6 +228,19 @@ export const upsertMessage = (context, key, accountKey, chatKey, message, via, s
|
|
|
215
228
|
});
|
|
216
229
|
if (Number(heardFirst) > 0)
|
|
217
230
|
intoVoiceAttachment(context, pk);
|
|
231
|
+
return !found;
|
|
232
|
+
};
|
|
233
|
+
/**
|
|
234
|
+
* One more unread for a message from someone else that the store first saw now, sent after the messenger's count was
|
|
235
|
+
* taken. A chat the messenger never gave a count for is left alone: the store would count its whole history.
|
|
236
|
+
*/
|
|
237
|
+
export const countUnread = ({ database }, chatKey, message) => {
|
|
238
|
+
if (message.outgoing || message.editedAt !== null)
|
|
239
|
+
return;
|
|
240
|
+
database
|
|
241
|
+
.prepare(`UPDATE chats SET unread_count = unread_count + 1
|
|
242
|
+
WHERE id = ? AND unread_count IS NOT NULL AND unread_counted_at IS NOT NULL AND unread_counted_at < ?`)
|
|
243
|
+
.run(chatKey, Date.parse(message.timestamp));
|
|
218
244
|
};
|
|
219
245
|
/**
|
|
220
246
|
* The owner's ruling (NEED-393 A): a deleted message keeps its row, so a later sync cannot bring it
|
|
@@ -237,6 +263,9 @@ export const tombstone = (context, pk) => {
|
|
|
237
263
|
context.database
|
|
238
264
|
.prepare("DELETE FROM poll_votes WHERE chat_id=? AND message_external_id=?")
|
|
239
265
|
.run(row.chatId, row.externalId);
|
|
266
|
+
context.database
|
|
267
|
+
.prepare("DELETE FROM message_reactions WHERE chat_id=? AND message_external_id=?")
|
|
268
|
+
.run(row.chatId, row.externalId);
|
|
240
269
|
purgeVectorsOf(context, pk);
|
|
241
270
|
orm
|
|
242
271
|
.delete(messageTranscripts)
|
|
@@ -244,10 +273,10 @@ export const tombstone = (context, pk) => {
|
|
|
244
273
|
.run();
|
|
245
274
|
return 1;
|
|
246
275
|
};
|
|
247
|
-
/** Whether the message is held at all. */
|
|
248
|
-
export const saveReactions = (
|
|
276
|
+
/** Whether the message is held at all. Where people's own reactions are held, the totals come from them instead. */
|
|
277
|
+
export const saveReactions = (context, chatKey, messageId, reactions) => context.orm
|
|
249
278
|
.update(messages)
|
|
250
|
-
.set({ reactions: JSON.stringify(reactions) })
|
|
279
|
+
.set({ reactions: JSON.stringify(derivedReactions(context, chatKey, messageId) ?? reactions) })
|
|
251
280
|
.where(and(eq(messages.chatId, chatKey), eq(messages.externalId, messageId), isNull(messages.deletedAt)))
|
|
252
281
|
.returning({ pk: messages.id })
|
|
253
282
|
.all().length > 0;
|
|
@@ -258,6 +287,14 @@ export const saveStar = ({ orm }, chatKey, messageId, starred) => orm
|
|
|
258
287
|
.where(and(eq(messages.chatId, chatKey), eq(messages.externalId, messageId), isNull(messages.deletedAt)))
|
|
259
288
|
.returning({ pk: messages.id })
|
|
260
289
|
.all().length > 0;
|
|
290
|
+
const raised = (delivery) => sql `max(coalesce(delivery, 0), ${DELIVERY[delivery]})`;
|
|
291
|
+
/** Answers whether the owner's own message is held. A receipt is for a message the owner sent, so another's keeps none. */
|
|
292
|
+
export const saveDelivery = ({ orm }, chatKey, messageId, delivery) => orm
|
|
293
|
+
.update(messages)
|
|
294
|
+
.set({ delivery: raised(delivery) })
|
|
295
|
+
.where(and(eq(messages.chatId, chatKey), eq(messages.externalId, messageId), eq(messages.outgoing, 1), isNull(messages.deletedAt)))
|
|
296
|
+
.returning({ pk: messages.id })
|
|
297
|
+
.all().length > 0;
|
|
261
298
|
export const markDeleted = (context, accountKey, messageIds, chatId, among) => {
|
|
262
299
|
const { orm } = context;
|
|
263
300
|
const chatKey = chatId === undefined ? undefined : findChatPk(context, accountKey, chatId);
|
|
@@ -326,3 +363,64 @@ export const pollVotes = ({ database }, chatKey, messageId) => database
|
|
|
326
363
|
answers: JSON.parse(answers),
|
|
327
364
|
votedAt: new Date(votedAt).toISOString(),
|
|
328
365
|
}));
|
|
366
|
+
const reactionStatements = new WeakMap();
|
|
367
|
+
const reactionStatementsOf = (database) => {
|
|
368
|
+
const held = reactionStatements.get(database);
|
|
369
|
+
if (held)
|
|
370
|
+
return held;
|
|
371
|
+
const statements = {
|
|
372
|
+
gone: database.prepare("SELECT 1 FROM messages WHERE chat_id=? AND external_id=? AND deleted_at IS NOT NULL"),
|
|
373
|
+
upsert: database.prepare(`INSERT INTO message_reactions
|
|
374
|
+
(chat_id, message_external_id, identity_id, own, reaction, reacted_at, created_at, updated_at)
|
|
375
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
376
|
+
ON CONFLICT (chat_id, message_external_id, identity_id) DO UPDATE SET
|
|
377
|
+
own = excluded.own, reaction = excluded.reaction, reacted_at = excluded.reacted_at,
|
|
378
|
+
updated_at = excluded.updated_at
|
|
379
|
+
WHERE excluded.reacted_at >= message_reactions.reacted_at`),
|
|
380
|
+
held: database.prepare("SELECT reaction, own, reacted_at AS reactedAt FROM message_reactions WHERE chat_id=? AND message_external_id=?"),
|
|
381
|
+
total: database.prepare("UPDATE messages SET reactions=? WHERE chat_id=? AND external_id=? AND deleted_at IS NULL"),
|
|
382
|
+
};
|
|
383
|
+
reactionStatements.set(database, statements);
|
|
384
|
+
return statements;
|
|
385
|
+
};
|
|
386
|
+
/**
|
|
387
|
+
* The totals of the people's reactions the store holds for a message, or `undefined` when it holds none: then the
|
|
388
|
+
* messenger's own totals stand. A taken-back reaction still counts as held, so all of them taken back is zero.
|
|
389
|
+
*/
|
|
390
|
+
export const derivedReactions = ({ database }, chatKey, messageId) => {
|
|
391
|
+
const rows = reactionStatementsOf(database).held.all(chatKey, messageId);
|
|
392
|
+
if (rows.length === 0)
|
|
393
|
+
return undefined;
|
|
394
|
+
const counts = new Map();
|
|
395
|
+
let mine = null;
|
|
396
|
+
for (const { reaction, own, reactedAt } of rows) {
|
|
397
|
+
if (reaction === null)
|
|
398
|
+
continue;
|
|
399
|
+
const held = counts.get(reaction);
|
|
400
|
+
counts.set(reaction, { count: (held?.count ?? 0) + 1, first: Math.min(held?.first ?? reactedAt, reactedAt) });
|
|
401
|
+
if (own === 1 && (mine === null || reactedAt > mine.at))
|
|
402
|
+
mine = { reaction, at: reactedAt };
|
|
403
|
+
}
|
|
404
|
+
const sorted = [...counts].sort(([, a], [, b]) => b.count - a.count || a.first - b.first);
|
|
405
|
+
return {
|
|
406
|
+
counts: sorted.map(([reaction, { count }]) => ({ reaction, count })),
|
|
407
|
+
mine: mine?.reaction ?? null,
|
|
408
|
+
total: sorted.reduce((sum, [, { count }]) => sum + count, 0),
|
|
409
|
+
};
|
|
410
|
+
};
|
|
411
|
+
/** A reaction older than the one held for that person is dropped, and so is any reaction to a deleted message. */
|
|
412
|
+
export const saveReaction = (context, accountKey, provider, chatKey, messageId, { person, own, reaction, reactedAt }) => {
|
|
413
|
+
const { database, now } = context;
|
|
414
|
+
const statements = reactionStatementsOf(database);
|
|
415
|
+
if (statements.gone.get(chatKey, messageId))
|
|
416
|
+
return;
|
|
417
|
+
const reactor = identityPk(context, accountKey, provider, person.id, person.name, person.username ? { username: person.username } : {});
|
|
418
|
+
const at = toMs(reactedAt) ?? now();
|
|
419
|
+
statements.upsert.run(chatKey, messageId, reactor, own ? 1 : 0, reaction, at, now(), now());
|
|
420
|
+
writeDerivedReactions(context, chatKey, messageId);
|
|
421
|
+
};
|
|
422
|
+
export const writeDerivedReactions = (context, chatKey, messageId) => {
|
|
423
|
+
const derived = derivedReactions(context, chatKey, messageId);
|
|
424
|
+
if (derived)
|
|
425
|
+
reactionStatementsOf(context.database).total.run(JSON.stringify(derived), chatKey, messageId);
|
|
426
|
+
};
|
|
@@ -554,5 +554,28 @@ export const GENERATED = [
|
|
|
554
554
|
"CREATE TRIGGER chat_revisions_au AFTER UPDATE OF external_id, title, membership_state, scope, searchable, parent_chat_id ON chats BEGIN\n INSERT INTO chat_revisions (chat_id, account_id, field, old_value, new_value, source, created_at)\n SELECT new.id, new.account_id, f.field, f.old_value, f.new_value, s.source,\n coalesce(s.at, CAST((julianday('now') - 2440587.5) * 86400000 AS INTEGER))\n FROM (\n SELECT 'external_id' AS field, old.external_id AS old_value, new.external_id AS new_value\n UNION ALL SELECT 'title', old.title, new.title\n UNION ALL SELECT 'membership_state', old.membership_state, new.membership_state\n UNION ALL SELECT 'scope', old.scope, new.scope\n UNION ALL SELECT 'searchable', old.searchable, new.searchable\n UNION ALL SELECT 'parent_chat_id', old.parent_chat_id, new.parent_chat_id\n ) f\n LEFT JOIN chat_revision_source s ON s.id = 1\n WHERE f.old_value IS NOT f.new_value;\nEND;",
|
|
555
555
|
"CREATE TRIGGER chat_revisions_ad AFTER DELETE ON chats BEGIN\n DELETE FROM chat_revisions WHERE chat_id = old.id;\nEND;"
|
|
556
556
|
]
|
|
557
|
+
},
|
|
558
|
+
{
|
|
559
|
+
"name": "20261021214318_version-26-message-reactions",
|
|
560
|
+
"statements": [
|
|
561
|
+
"CREATE TABLE `message_reactions` (\n\t`id` integer PRIMARY KEY,\n\t`chat_id` integer NOT NULL,\n\t`message_external_id` text NOT NULL,\n\t`identity_id` integer NOT NULL,\n\t`own` integer NOT NULL,\n\t`reaction` text,\n\t`reacted_at` integer NOT NULL,\n\t`created_at` integer NOT NULL,\n\t`updated_at` integer NOT NULL,\n\tCONSTRAINT `fk_message_reactions_chat_id_chats_id_fk` FOREIGN KEY (`chat_id`) REFERENCES `chats`(`id`),\n\tCONSTRAINT `fk_message_reactions_identity_id_identities_id_fk` FOREIGN KEY (`identity_id`) REFERENCES `identities`(`id`),\n\tCONSTRAINT `message_reactions_chat_id_message_external_id_identity_id_unique` UNIQUE(`chat_id`,`message_external_id`,`identity_id`)\n);",
|
|
562
|
+
"CREATE INDEX `message_reactions_by_identity_id` ON `message_reactions` (`identity_id`);"
|
|
563
|
+
]
|
|
564
|
+
},
|
|
565
|
+
{
|
|
566
|
+
"name": "20261021221808_version-27-message-delivery",
|
|
567
|
+
"statements": [
|
|
568
|
+
"ALTER TABLE `messages` ADD `delivery` integer;"
|
|
569
|
+
]
|
|
570
|
+
},
|
|
571
|
+
{
|
|
572
|
+
"name": "20261021224625_version-28-chat-state",
|
|
573
|
+
"statements": [
|
|
574
|
+
"ALTER TABLE `chats` ADD `muted` integer;",
|
|
575
|
+
"ALTER TABLE `chats` ADD `muted_until` integer;",
|
|
576
|
+
"ALTER TABLE `chats` ADD `archived` integer;",
|
|
577
|
+
"ALTER TABLE `chats` ADD `marked_unread` integer;",
|
|
578
|
+
"ALTER TABLE `chats` ADD `unread_counted_at` integer;"
|
|
579
|
+
]
|
|
557
580
|
}
|
|
558
581
|
];
|
|
@@ -13,6 +13,20 @@ import { assertReadCompatible } from "./read-compatibility.js";
|
|
|
13
13
|
import { stemsState } from "./stems.js";
|
|
14
14
|
import { fillStems, followDefaultStemmers } from "./stems-fill.js";
|
|
15
15
|
export const BACKFILL_ON_OPEN = 5_000;
|
|
16
|
+
const NOT_WRITABLE = new Set(["EACCES", "EPERM", "EROFS"]);
|
|
17
|
+
const createWritable = (file) => {
|
|
18
|
+
try {
|
|
19
|
+
mkdirSync(dirname(file), { recursive: true, mode: 0o700 });
|
|
20
|
+
// SQLite gives its sidecar files the database's permissions.
|
|
21
|
+
writeFileSync(file, "", { flag: "a", mode: 0o600 });
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
if (!NOT_WRITABLE.has(error.code ?? ""))
|
|
25
|
+
throw error;
|
|
26
|
+
// The errno's own sentence names the path, which holds the owner's home folder.
|
|
27
|
+
throw new CliError("permission_error", "the message store cannot be written from here; run where it is writable, or set MESSAGING_STORE", { reason: "store.not-writable" });
|
|
28
|
+
}
|
|
29
|
+
};
|
|
16
30
|
export const openStoreContext = async ({ path, env, now = Date.now, command, readOnly = false, } = {}) => {
|
|
17
31
|
if (typeof readOnly !== "boolean")
|
|
18
32
|
throw new CliError("validation_error", "readOnly must be a boolean");
|
|
@@ -22,11 +36,8 @@ export const openStoreContext = async ({ path, env, now = Date.now, command, rea
|
|
|
22
36
|
if (!statSync(file, { throwIfNoEntry: false })?.isFile())
|
|
23
37
|
throw new CliError("not_found", "The store does not exist as a file; read-only opening does not create it");
|
|
24
38
|
}
|
|
25
|
-
else
|
|
26
|
-
|
|
27
|
-
// SQLite gives its sidecar files the database's permissions.
|
|
28
|
-
writeFileSync(file, "", { flag: "a", mode: 0o600 });
|
|
29
|
-
}
|
|
39
|
+
else
|
|
40
|
+
createWritable(file);
|
|
30
41
|
const opened = await openSqlite(file, { readOnly });
|
|
31
42
|
const database = opened.database;
|
|
32
43
|
try {
|
|
@@ -266,6 +266,22 @@ export declare const MESSAGE_FIELDS: {
|
|
|
266
266
|
identity: undefined;
|
|
267
267
|
generated: undefined;
|
|
268
268
|
}, {}>;
|
|
269
|
+
delivery: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
270
|
+
name: string;
|
|
271
|
+
tableName: "messages";
|
|
272
|
+
dataType: "number int53";
|
|
273
|
+
data: number;
|
|
274
|
+
driverParam: number;
|
|
275
|
+
notNull: false;
|
|
276
|
+
hasDefault: false;
|
|
277
|
+
isPrimaryKey: false;
|
|
278
|
+
isAutoincrement: false;
|
|
279
|
+
hasRuntimeDefault: false;
|
|
280
|
+
enumValues: undefined;
|
|
281
|
+
baseColumn: never;
|
|
282
|
+
identity: undefined;
|
|
283
|
+
generated: undefined;
|
|
284
|
+
}, {}>;
|
|
269
285
|
};
|
|
270
286
|
export interface MessageRow {
|
|
271
287
|
pk: number;
|
|
@@ -286,6 +302,7 @@ export interface MessageRow {
|
|
|
286
302
|
providerMetadata: string | null;
|
|
287
303
|
mentions: string | null;
|
|
288
304
|
starred: number | null;
|
|
305
|
+
delivery: number | null;
|
|
289
306
|
}
|
|
290
307
|
export declare const selectMessages: ({ orm }: StoreContext) => Omit<import("drizzle-orm/sqlite-core").SQLiteAsyncSelectBase<"messages", "sync", unknown, {
|
|
291
308
|
pk: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
@@ -546,6 +563,22 @@ export declare const selectMessages: ({ orm }: StoreContext) => Omit<import("dri
|
|
|
546
563
|
identity: undefined;
|
|
547
564
|
generated: undefined;
|
|
548
565
|
}, {}>;
|
|
566
|
+
delivery: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
567
|
+
name: string;
|
|
568
|
+
tableName: "messages";
|
|
569
|
+
dataType: "number int53";
|
|
570
|
+
data: number;
|
|
571
|
+
driverParam: number;
|
|
572
|
+
notNull: false;
|
|
573
|
+
hasDefault: false;
|
|
574
|
+
isPrimaryKey: false;
|
|
575
|
+
isAutoincrement: false;
|
|
576
|
+
hasRuntimeDefault: false;
|
|
577
|
+
enumValues: undefined;
|
|
578
|
+
baseColumn: never;
|
|
579
|
+
identity: undefined;
|
|
580
|
+
generated: undefined;
|
|
581
|
+
}, {}>;
|
|
549
582
|
}, "partial", Record<"messages", "not-null"> & {
|
|
550
583
|
chats: "not-null";
|
|
551
584
|
} & {
|
|
@@ -569,6 +602,7 @@ export declare const selectMessages: ({ orm }: StoreContext) => Omit<import("dri
|
|
|
569
602
|
providerMetadata: string | null;
|
|
570
603
|
mentions: string | null;
|
|
571
604
|
starred: number | null;
|
|
605
|
+
delivery: number | null;
|
|
572
606
|
}[], {
|
|
573
607
|
pk: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
574
608
|
name: string;
|
|
@@ -828,6 +862,22 @@ export declare const selectMessages: ({ orm }: StoreContext) => Omit<import("dri
|
|
|
828
862
|
identity: undefined;
|
|
829
863
|
generated: undefined;
|
|
830
864
|
}, {}>;
|
|
865
|
+
delivery: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
866
|
+
name: string;
|
|
867
|
+
tableName: "messages";
|
|
868
|
+
dataType: "number int53";
|
|
869
|
+
data: number;
|
|
870
|
+
driverParam: number;
|
|
871
|
+
notNull: false;
|
|
872
|
+
hasDefault: false;
|
|
873
|
+
isPrimaryKey: false;
|
|
874
|
+
isAutoincrement: false;
|
|
875
|
+
hasRuntimeDefault: false;
|
|
876
|
+
enumValues: undefined;
|
|
877
|
+
baseColumn: never;
|
|
878
|
+
identity: undefined;
|
|
879
|
+
generated: undefined;
|
|
880
|
+
}, {}>;
|
|
831
881
|
}>, never>;
|
|
832
882
|
export declare const newestFirst: SQL<unknown>[];
|
|
833
883
|
export declare const before: (sentAt: number, pk: number) => SQL<unknown>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
2
|
import { and, asc, desc, eq, gt, gte, inArray, isNull, lte, or, sql } from "./drizzle/core.js";
|
|
3
3
|
import { attachments, chats, identities, messageRevisions, messages } from "./schema.js";
|
|
4
|
-
import { parsed, present, toIso, toMs } from "./values.js";
|
|
4
|
+
import { deliveryOf, parsed, present, toIso, toMs } from "./values.js";
|
|
5
5
|
/**
|
|
6
6
|
* One flat row for every read, aliased where tables share a column name, so the same mapper serves
|
|
7
7
|
* a plain select and a subquery (`find` per chat).
|
|
@@ -25,6 +25,7 @@ export const MESSAGE_FIELDS = {
|
|
|
25
25
|
providerMetadata: messages.metadata,
|
|
26
26
|
mentions: messages.mentions,
|
|
27
27
|
starred: messages.starred,
|
|
28
|
+
delivery: messages.delivery,
|
|
28
29
|
};
|
|
29
30
|
export const selectMessages = ({ orm }) => orm
|
|
30
31
|
.select(MESSAGE_FIELDS)
|
|
@@ -236,6 +237,7 @@ const toMessage = (row, attachments) => {
|
|
|
236
237
|
reactions: parsed(row.reactions) ?? null,
|
|
237
238
|
...present({ providerMetadata: parsed(row.providerMetadata), mentions: parsed(row.mentions) }),
|
|
238
239
|
...(row.starred === 1 ? { starred: true } : {}),
|
|
240
|
+
...present({ delivery: deliveryOf(row.delivery) }),
|
|
239
241
|
};
|
|
240
242
|
};
|
|
241
243
|
/**
|
|
@@ -2596,6 +2596,86 @@ export declare const chats: import("drizzle-orm/sqlite-core").SQLiteTableWithCol
|
|
|
2596
2596
|
identity: undefined;
|
|
2597
2597
|
generated: undefined;
|
|
2598
2598
|
}, {}>;
|
|
2599
|
+
muted: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
2600
|
+
name: string;
|
|
2601
|
+
tableName: "chats";
|
|
2602
|
+
dataType: "number int53";
|
|
2603
|
+
data: number;
|
|
2604
|
+
driverParam: number;
|
|
2605
|
+
notNull: false;
|
|
2606
|
+
hasDefault: false;
|
|
2607
|
+
isPrimaryKey: false;
|
|
2608
|
+
isAutoincrement: false;
|
|
2609
|
+
hasRuntimeDefault: false;
|
|
2610
|
+
enumValues: undefined;
|
|
2611
|
+
baseColumn: never;
|
|
2612
|
+
identity: undefined;
|
|
2613
|
+
generated: undefined;
|
|
2614
|
+
}, {}>;
|
|
2615
|
+
mutedUntil: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
2616
|
+
name: string;
|
|
2617
|
+
tableName: "chats";
|
|
2618
|
+
dataType: "number int53";
|
|
2619
|
+
data: number;
|
|
2620
|
+
driverParam: number;
|
|
2621
|
+
notNull: false;
|
|
2622
|
+
hasDefault: false;
|
|
2623
|
+
isPrimaryKey: false;
|
|
2624
|
+
isAutoincrement: false;
|
|
2625
|
+
hasRuntimeDefault: false;
|
|
2626
|
+
enumValues: undefined;
|
|
2627
|
+
baseColumn: never;
|
|
2628
|
+
identity: undefined;
|
|
2629
|
+
generated: undefined;
|
|
2630
|
+
}, {}>;
|
|
2631
|
+
archived: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
2632
|
+
name: string;
|
|
2633
|
+
tableName: "chats";
|
|
2634
|
+
dataType: "number int53";
|
|
2635
|
+
data: number;
|
|
2636
|
+
driverParam: number;
|
|
2637
|
+
notNull: false;
|
|
2638
|
+
hasDefault: false;
|
|
2639
|
+
isPrimaryKey: false;
|
|
2640
|
+
isAutoincrement: false;
|
|
2641
|
+
hasRuntimeDefault: false;
|
|
2642
|
+
enumValues: undefined;
|
|
2643
|
+
baseColumn: never;
|
|
2644
|
+
identity: undefined;
|
|
2645
|
+
generated: undefined;
|
|
2646
|
+
}, {}>;
|
|
2647
|
+
markedUnread: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
2648
|
+
name: string;
|
|
2649
|
+
tableName: "chats";
|
|
2650
|
+
dataType: "number int53";
|
|
2651
|
+
data: number;
|
|
2652
|
+
driverParam: number;
|
|
2653
|
+
notNull: false;
|
|
2654
|
+
hasDefault: false;
|
|
2655
|
+
isPrimaryKey: false;
|
|
2656
|
+
isAutoincrement: false;
|
|
2657
|
+
hasRuntimeDefault: false;
|
|
2658
|
+
enumValues: undefined;
|
|
2659
|
+
baseColumn: never;
|
|
2660
|
+
identity: undefined;
|
|
2661
|
+
generated: undefined;
|
|
2662
|
+
}, {}>;
|
|
2663
|
+
unreadCountedAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
2664
|
+
name: string;
|
|
2665
|
+
tableName: "chats";
|
|
2666
|
+
dataType: "number int53";
|
|
2667
|
+
data: number;
|
|
2668
|
+
driverParam: number;
|
|
2669
|
+
notNull: false;
|
|
2670
|
+
hasDefault: false;
|
|
2671
|
+
isPrimaryKey: false;
|
|
2672
|
+
isAutoincrement: false;
|
|
2673
|
+
hasRuntimeDefault: false;
|
|
2674
|
+
enumValues: undefined;
|
|
2675
|
+
baseColumn: never;
|
|
2676
|
+
identity: undefined;
|
|
2677
|
+
generated: undefined;
|
|
2678
|
+
}, {}>;
|
|
2599
2679
|
};
|
|
2600
2680
|
dialect: 'sqlite';
|
|
2601
2681
|
}>;
|
|
@@ -3365,6 +3445,22 @@ export declare const messages: import("drizzle-orm/sqlite-core").SQLiteTableWith
|
|
|
3365
3445
|
identity: undefined;
|
|
3366
3446
|
generated: undefined;
|
|
3367
3447
|
}, {}>;
|
|
3448
|
+
delivery: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
3449
|
+
name: string;
|
|
3450
|
+
tableName: "messages";
|
|
3451
|
+
dataType: "number int53";
|
|
3452
|
+
data: number;
|
|
3453
|
+
driverParam: number;
|
|
3454
|
+
notNull: false;
|
|
3455
|
+
hasDefault: false;
|
|
3456
|
+
isPrimaryKey: false;
|
|
3457
|
+
isAutoincrement: false;
|
|
3458
|
+
hasRuntimeDefault: false;
|
|
3459
|
+
enumValues: undefined;
|
|
3460
|
+
baseColumn: never;
|
|
3461
|
+
identity: undefined;
|
|
3462
|
+
generated: undefined;
|
|
3463
|
+
}, {}>;
|
|
3368
3464
|
};
|
|
3369
3465
|
dialect: 'sqlite';
|
|
3370
3466
|
}>;
|
|
@@ -4478,6 +4574,161 @@ export declare const chatEventPeople: import("drizzle-orm/sqlite-core").SQLiteTa
|
|
|
4478
4574
|
};
|
|
4479
4575
|
dialect: 'sqlite';
|
|
4480
4576
|
}>;
|
|
4577
|
+
/**
|
|
4578
|
+
* Each person's newest reaction to a message, for a messenger that sends one person's change at a time; the store
|
|
4579
|
+
* derives `messages.reactions` from them. Keyed by chat and external id: a reaction can arrive before its message.
|
|
4580
|
+
*/
|
|
4581
|
+
export declare const messageReactions: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
|
|
4582
|
+
name: "message_reactions";
|
|
4583
|
+
schema: undefined;
|
|
4584
|
+
columns: {
|
|
4585
|
+
id: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
4586
|
+
name: string;
|
|
4587
|
+
tableName: "message_reactions";
|
|
4588
|
+
dataType: "number int53";
|
|
4589
|
+
data: number;
|
|
4590
|
+
driverParam: number;
|
|
4591
|
+
notNull: true;
|
|
4592
|
+
hasDefault: true;
|
|
4593
|
+
isPrimaryKey: true;
|
|
4594
|
+
isAutoincrement: false;
|
|
4595
|
+
hasRuntimeDefault: false;
|
|
4596
|
+
enumValues: undefined;
|
|
4597
|
+
baseColumn: never;
|
|
4598
|
+
identity: undefined;
|
|
4599
|
+
generated: undefined;
|
|
4600
|
+
}, {}>;
|
|
4601
|
+
chatId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
4602
|
+
name: string;
|
|
4603
|
+
tableName: "message_reactions";
|
|
4604
|
+
dataType: "number int53";
|
|
4605
|
+
data: number;
|
|
4606
|
+
driverParam: number;
|
|
4607
|
+
notNull: true;
|
|
4608
|
+
hasDefault: false;
|
|
4609
|
+
isPrimaryKey: false;
|
|
4610
|
+
isAutoincrement: false;
|
|
4611
|
+
hasRuntimeDefault: false;
|
|
4612
|
+
enumValues: undefined;
|
|
4613
|
+
baseColumn: never;
|
|
4614
|
+
identity: undefined;
|
|
4615
|
+
generated: undefined;
|
|
4616
|
+
}, {}>;
|
|
4617
|
+
messageExternalId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
4618
|
+
name: string;
|
|
4619
|
+
tableName: "message_reactions";
|
|
4620
|
+
dataType: "string";
|
|
4621
|
+
data: string;
|
|
4622
|
+
driverParam: string;
|
|
4623
|
+
notNull: true;
|
|
4624
|
+
hasDefault: false;
|
|
4625
|
+
isPrimaryKey: false;
|
|
4626
|
+
isAutoincrement: false;
|
|
4627
|
+
hasRuntimeDefault: false;
|
|
4628
|
+
enumValues: [string, ...string[]];
|
|
4629
|
+
baseColumn: never;
|
|
4630
|
+
identity: undefined;
|
|
4631
|
+
generated: undefined;
|
|
4632
|
+
}, {}>;
|
|
4633
|
+
identityId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
4634
|
+
name: string;
|
|
4635
|
+
tableName: "message_reactions";
|
|
4636
|
+
dataType: "number int53";
|
|
4637
|
+
data: number;
|
|
4638
|
+
driverParam: number;
|
|
4639
|
+
notNull: true;
|
|
4640
|
+
hasDefault: false;
|
|
4641
|
+
isPrimaryKey: false;
|
|
4642
|
+
isAutoincrement: false;
|
|
4643
|
+
hasRuntimeDefault: false;
|
|
4644
|
+
enumValues: undefined;
|
|
4645
|
+
baseColumn: never;
|
|
4646
|
+
identity: undefined;
|
|
4647
|
+
generated: undefined;
|
|
4648
|
+
}, {}>;
|
|
4649
|
+
own: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
4650
|
+
name: string;
|
|
4651
|
+
tableName: "message_reactions";
|
|
4652
|
+
dataType: "number int53";
|
|
4653
|
+
data: number;
|
|
4654
|
+
driverParam: number;
|
|
4655
|
+
notNull: true;
|
|
4656
|
+
hasDefault: false;
|
|
4657
|
+
isPrimaryKey: false;
|
|
4658
|
+
isAutoincrement: false;
|
|
4659
|
+
hasRuntimeDefault: false;
|
|
4660
|
+
enumValues: undefined;
|
|
4661
|
+
baseColumn: never;
|
|
4662
|
+
identity: undefined;
|
|
4663
|
+
generated: undefined;
|
|
4664
|
+
}, {}>;
|
|
4665
|
+
reaction: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
4666
|
+
name: string;
|
|
4667
|
+
tableName: "message_reactions";
|
|
4668
|
+
dataType: "string";
|
|
4669
|
+
data: string;
|
|
4670
|
+
driverParam: string;
|
|
4671
|
+
notNull: false;
|
|
4672
|
+
hasDefault: false;
|
|
4673
|
+
isPrimaryKey: false;
|
|
4674
|
+
isAutoincrement: false;
|
|
4675
|
+
hasRuntimeDefault: false;
|
|
4676
|
+
enumValues: [string, ...string[]];
|
|
4677
|
+
baseColumn: never;
|
|
4678
|
+
identity: undefined;
|
|
4679
|
+
generated: undefined;
|
|
4680
|
+
}, {}>;
|
|
4681
|
+
reactedAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
4682
|
+
name: string;
|
|
4683
|
+
tableName: "message_reactions";
|
|
4684
|
+
dataType: "number int53";
|
|
4685
|
+
data: number;
|
|
4686
|
+
driverParam: number;
|
|
4687
|
+
notNull: true;
|
|
4688
|
+
hasDefault: false;
|
|
4689
|
+
isPrimaryKey: false;
|
|
4690
|
+
isAutoincrement: false;
|
|
4691
|
+
hasRuntimeDefault: false;
|
|
4692
|
+
enumValues: undefined;
|
|
4693
|
+
baseColumn: never;
|
|
4694
|
+
identity: undefined;
|
|
4695
|
+
generated: undefined;
|
|
4696
|
+
}, {}>;
|
|
4697
|
+
createdAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
4698
|
+
name: string;
|
|
4699
|
+
tableName: "message_reactions";
|
|
4700
|
+
dataType: "number int53";
|
|
4701
|
+
data: number;
|
|
4702
|
+
driverParam: number;
|
|
4703
|
+
notNull: true;
|
|
4704
|
+
hasDefault: false;
|
|
4705
|
+
isPrimaryKey: false;
|
|
4706
|
+
isAutoincrement: false;
|
|
4707
|
+
hasRuntimeDefault: false;
|
|
4708
|
+
enumValues: undefined;
|
|
4709
|
+
baseColumn: never;
|
|
4710
|
+
identity: undefined;
|
|
4711
|
+
generated: undefined;
|
|
4712
|
+
}, {}>;
|
|
4713
|
+
updatedAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
4714
|
+
name: string;
|
|
4715
|
+
tableName: "message_reactions";
|
|
4716
|
+
dataType: "number int53";
|
|
4717
|
+
data: number;
|
|
4718
|
+
driverParam: number;
|
|
4719
|
+
notNull: true;
|
|
4720
|
+
hasDefault: false;
|
|
4721
|
+
isPrimaryKey: false;
|
|
4722
|
+
isAutoincrement: false;
|
|
4723
|
+
hasRuntimeDefault: false;
|
|
4724
|
+
enumValues: undefined;
|
|
4725
|
+
baseColumn: never;
|
|
4726
|
+
identity: undefined;
|
|
4727
|
+
generated: undefined;
|
|
4728
|
+
}, {}>;
|
|
4729
|
+
};
|
|
4730
|
+
dialect: 'sqlite';
|
|
4731
|
+
}>;
|
|
4481
4732
|
/** One read of a chat's member list. Retention reads presence at a checkpoint from these, not from stays. */
|
|
4482
4733
|
export declare const memberObservations: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
|
|
4483
4734
|
name: "member_observations";
|
|
@@ -261,6 +261,11 @@ export const chats = sqliteTable("chats", {
|
|
|
261
261
|
createdAt: integer("created_at").notNull(),
|
|
262
262
|
parentChatId: integer("parent_chat_id").references(() => chats.id),
|
|
263
263
|
scope: text("scope"),
|
|
264
|
+
muted: integer("muted"),
|
|
265
|
+
mutedUntil: integer("muted_until"),
|
|
266
|
+
archived: integer("archived"),
|
|
267
|
+
markedUnread: integer("marked_unread"),
|
|
268
|
+
unreadCountedAt: integer("unread_counted_at"),
|
|
264
269
|
}, (table) => [
|
|
265
270
|
unique().on(table.accountId, table.externalId),
|
|
266
271
|
index("chats_by_recency").on(table.accountId, desc(table.lastMessageAt)),
|
|
@@ -348,6 +353,7 @@ export const messages = sqliteTable("messages", {
|
|
|
348
353
|
updatedAt: integer("updated_at").notNull(),
|
|
349
354
|
threadRootId: integer("thread_root_id").references(() => messages.id),
|
|
350
355
|
starred: integer("starred"),
|
|
356
|
+
delivery: integer("delivery"),
|
|
351
357
|
}, (table) => [
|
|
352
358
|
unique().on(table.chatId, table.externalId),
|
|
353
359
|
index("messages_by_time").on(table.chatId, desc(table.sentAt)),
|
|
@@ -511,6 +517,28 @@ export const chatEventPeople = sqliteTable("chat_event_people", {
|
|
|
511
517
|
primaryKey({ columns: [table.chatEventId, table.identityId] }),
|
|
512
518
|
index("chat_event_people_by_identity_id").on(table.identityId),
|
|
513
519
|
]);
|
|
520
|
+
/**
|
|
521
|
+
* Each person's newest reaction to a message, for a messenger that sends one person's change at a time; the store
|
|
522
|
+
* derives `messages.reactions` from them. Keyed by chat and external id: a reaction can arrive before its message.
|
|
523
|
+
*/
|
|
524
|
+
export const messageReactions = sqliteTable("message_reactions", {
|
|
525
|
+
id: integer("id").primaryKey(),
|
|
526
|
+
chatId: integer("chat_id")
|
|
527
|
+
.notNull()
|
|
528
|
+
.references(() => chats.id),
|
|
529
|
+
messageExternalId: text("message_external_id").notNull(),
|
|
530
|
+
identityId: integer("identity_id")
|
|
531
|
+
.notNull()
|
|
532
|
+
.references(() => identities.id),
|
|
533
|
+
own: integer("own").notNull(),
|
|
534
|
+
reaction: text("reaction"),
|
|
535
|
+
reactedAt: integer("reacted_at").notNull(),
|
|
536
|
+
createdAt: integer("created_at").notNull(),
|
|
537
|
+
updatedAt: integer("updated_at").notNull(),
|
|
538
|
+
}, (table) => [
|
|
539
|
+
unique().on(table.chatId, table.messageExternalId, table.identityId),
|
|
540
|
+
index("message_reactions_by_identity_id").on(table.identityId),
|
|
541
|
+
]);
|
|
514
542
|
/** One read of a chat's member list. Retention reads presence at a checkpoint from these, not from stays. */
|
|
515
543
|
export const memberObservations = sqliteTable("member_observations", {
|
|
516
544
|
id: integer("id").primaryKey(),
|