@wirecat/cli-core 0.553.0 → 0.555.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 +32 -0
- package/dist/cli/messenger/catch-up-answers.d.ts +50 -1
- package/dist/cli/messenger/catch-up-answers.js +2 -1
- package/dist/cli/messenger/catch-up.d.ts +6 -2
- package/dist/cli/messenger/catch-up.js +11 -2
- package/dist/cli/messenger/conversation-answers.d.ts +3 -0
- package/dist/cli/messenger/inbox.js +1 -1
- package/dist/cli/messenger/message-answers.d.ts +3 -0
- package/dist/cli/messenger/message-writes.d.ts +6 -0
- package/dist/cli/messenger/points.d.ts +9 -4
- package/dist/cli/messenger/points.js +10 -4
- package/dist/cli/messenger/review.js +1 -1
- package/dist/cli/schedule/schedule-command.d.ts +0 -1
- package/dist/cli/schedule/schedule-command.js +6 -1
- package/dist/domain/models.d.ts +4 -0
- package/dist/entities/contact-context.d.ts +6 -0
- package/dist/entities/conversation-results.d.ts +6 -0
- package/dist/entities/evidence.d.ts +24 -0
- package/dist/entities/index.d.ts +263 -3
- package/dist/entities/index.js +1 -0
- package/dist/entities/listener.d.ts +12 -0
- package/dist/entities/message-context.d.ts +9 -0
- package/dist/entities/message-write.d.ts +12 -0
- package/dist/entities/message.d.ts +6 -0
- package/dist/entities/message.js +6 -0
- package/dist/entities/store-records.d.ts +3 -0
- package/dist/mail/cli/index.d.ts +2 -1
- package/dist/mail/cli/index.js +4 -1
- package/dist/mail/cli/review.d.ts +24 -0
- package/dist/mail/cli/review.js +204 -0
- package/dist/mail/email-input.d.ts +2 -0
- package/dist/mail/email-input.js +6 -1
- package/dist/mail/index.d.ts +2 -0
- package/dist/mail/index.js +2 -0
- package/dist/mail/owed.d.ts +11 -0
- package/dist/mail/owed.js +14 -0
- package/dist/mail/retry.js +2 -1
- package/dist/mail/review.d.ts +37 -0
- package/dist/mail/review.js +118 -0
- package/dist/mail/sync.js +5 -1
- package/dist/mail/testing/fake.d.ts +2 -0
- package/dist/mail/testing/fake.js +1 -1
- package/dist/services/agenda-mail.d.ts +15 -0
- package/dist/services/agenda-mail.js +66 -0
- package/dist/services/agenda.d.ts +4 -0
- package/dist/services/agenda.js +17 -7
- package/dist/store/exports.d.ts +1 -0
- package/dist/store/exports.js +1 -0
- package/dist/store/sqlite/email-activity.d.ts +62 -0
- package/dist/store/sqlite/email-activity.js +71 -0
- package/dist/store/sqlite/email-locations.d.ts +1 -0
- package/dist/store/sqlite/email-locations.js +4 -0
- package/dist/store/sqlite/emails.d.ts +9 -3
- package/dist/store/sqlite/emails.js +7 -14
- package/package.json +1 -1
package/dist/services/agenda.js
CHANGED
|
@@ -3,6 +3,7 @@ import { formatLocator, isLocator, parseLocator } from "../domain/locator.js";
|
|
|
3
3
|
import { formatReference } from "../domain/references.js";
|
|
4
4
|
import { agenda } from "../entities/agenda.js";
|
|
5
5
|
import { AGENDA_COLUMNS, inboxKey, queryFilter, } from "../store/store.js";
|
|
6
|
+
import { mailActivity } from "./agenda-mail.js";
|
|
6
7
|
import { chatCoverage } from "./chat-coverage.js";
|
|
7
8
|
import { CHAT_KINDS } from "./chats.js";
|
|
8
9
|
import { taskAccount } from "./task-rules.js";
|
|
@@ -36,6 +37,10 @@ export const AGENDA_QUERY = {
|
|
|
36
37
|
}),
|
|
37
38
|
defaults: ["chat_kind:(dialog OR group)", "claimed:(none OR mine)"],
|
|
38
39
|
};
|
|
40
|
+
/**
|
|
41
|
+
* An email account's chats are its threads, and whose turn one is follows `mailTurn` (`../mail/owed.ts`), not merely
|
|
42
|
+
* who wrote last: mail sent to many is never owed.
|
|
43
|
+
*/
|
|
39
44
|
export const agendaService = (deps, now = Date.now) => ({
|
|
40
45
|
show: async ({ dueWithin = DAY, waiting = AGENDA_DEFAULTS.waiting, unanswered = AGENDA_DEFAULTS.unanswered, within = 30 * DAY, query, holder, } = {}) => {
|
|
41
46
|
const [store, account] = [await deps.store(), await deps.account()];
|
|
@@ -91,9 +96,12 @@ export const agendaService = (deps, now = Date.now) => ({
|
|
|
91
96
|
createdAt: task.createdAt,
|
|
92
97
|
assignees,
|
|
93
98
|
}));
|
|
94
|
-
const
|
|
95
|
-
const activity =
|
|
99
|
+
const mail = account.provider === "email" ? await mailActivity(store, account, at - within, parts.chats) : undefined;
|
|
100
|
+
const activity = mail ??
|
|
101
|
+
(await store.chats.chatActivity(account, at - within, parts.chats ? queryFilter(parts.chats, AGENDA_COLUMNS, { command: "tasks agenda" }) : undefined));
|
|
102
|
+
const turnOf = (chat) => "turn" in chat ? chat.turn : chat.lastFromOwner === null ? null : chat.lastFromOwner ? "waiting" : "owed";
|
|
96
103
|
const chats = activity.chats.map(({ lastMessageId, ...chat }) => ({
|
|
104
|
+
turn: turnOf({ lastMessageId, ...chat }),
|
|
97
105
|
...chat,
|
|
98
106
|
lastMessage: lastMessageId === null
|
|
99
107
|
? null
|
|
@@ -107,12 +115,14 @@ export const agendaService = (deps, now = Date.now) => ({
|
|
|
107
115
|
}));
|
|
108
116
|
const silentFor = (chat) => at - Date.parse(chat.lastMessageAt);
|
|
109
117
|
const shown = {
|
|
110
|
-
owed: chats.filter((chat) => chat.
|
|
111
|
-
waiting: chats.filter((chat) => chat.
|
|
112
|
-
|
|
118
|
+
owed: chats.filter((chat) => chat.turn === "owed" && silentFor(chat) >= unanswered),
|
|
119
|
+
waiting: chats.filter((chat) => chat.turn === "waiting" && silentFor(chat) >= waiting),
|
|
120
|
+
// A thread with no stored email but drafts is not behind: mail sync stores every email it lists.
|
|
121
|
+
unsynced: mail ? [] : chats.filter((chat) => chat.lastMessage === null),
|
|
113
122
|
};
|
|
114
|
-
const
|
|
115
|
-
const
|
|
123
|
+
const ids = Object.values(shown).flatMap((list) => list.map(({ chatId }) => chatId));
|
|
124
|
+
const covered = new Map((mail ? mail.coverage(ids) : await chatCoverage(store, account, ids)).map((one) => [one.chatId, one]));
|
|
125
|
+
const withCoverage = (list) => list.map(({ turn: _, ...chat }) => ({ ...chat, coverage: covered.get(chat.chatId) }));
|
|
116
126
|
const inList = new Set((await store.tasks.categories()).map(({ name }) => name));
|
|
117
127
|
const unlisted = new Map();
|
|
118
128
|
for (const { category } of open)
|
package/dist/store/exports.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ export { storeClients } from "./sqlite/clients.js";
|
|
|
23
23
|
export { CHAT_LIST_KEY, type ChatCompleteness, fetchedKey, historyStartKey } from "./sqlite/completeness.js";
|
|
24
24
|
export { CONVERSATION_COLUMNS } from "./sqlite/conversations.js";
|
|
25
25
|
export type { CounterTarget } from "./sqlite/counters.js";
|
|
26
|
+
export { type EmailMarks, MAIL_AGENDA_COLUMNS, type NewestEmail, type ThreadActivity, } from "./sqlite/email-activity.js";
|
|
26
27
|
export { messageOfEmail, threadConversation } from "./sqlite/emails.js";
|
|
27
28
|
export { orphanPointers } from "./sqlite/entity-types.js";
|
|
28
29
|
export { involvementStoreOver } from "./sqlite/involvements.js";
|
package/dist/store/exports.js
CHANGED
|
@@ -6,6 +6,7 @@ export { CHAT_COLUMNS, TRACKED_CHAT_COLUMNS, titlePart } from "./sqlite/chats.js
|
|
|
6
6
|
export { storeClients } from "./sqlite/clients.js";
|
|
7
7
|
export { CHAT_LIST_KEY, fetchedKey, historyStartKey } from "./sqlite/completeness.js";
|
|
8
8
|
export { CONVERSATION_COLUMNS } from "./sqlite/conversations.js";
|
|
9
|
+
export { MAIL_AGENDA_COLUMNS, } from "./sqlite/email-activity.js";
|
|
9
10
|
export { messageOfEmail, threadConversation } from "./sqlite/emails.js";
|
|
10
11
|
export { orphanPointers } from "./sqlite/entity-types.js";
|
|
11
12
|
export { involvementStoreOver } from "./sqlite/involvements.js";
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { CacheDatabase } from "../driver.js";
|
|
2
|
+
import type { Where } from "./query-filter.js";
|
|
3
|
+
/** What decides whose turn a thread is, read from its newest email that is not a draft; never its text. */
|
|
4
|
+
export interface NewestEmail {
|
|
5
|
+
externalId: string;
|
|
6
|
+
/** ms: when it was received, or sent when that is unknown. */
|
|
7
|
+
at: number;
|
|
8
|
+
outgoing: boolean;
|
|
9
|
+
/** The mailbox's own address is in To or Cc. */
|
|
10
|
+
addressed: boolean;
|
|
11
|
+
/** Filed in the mailbox of mail sent to many. */
|
|
12
|
+
bulk: boolean;
|
|
13
|
+
/** Carries a List-Id. */
|
|
14
|
+
list: boolean;
|
|
15
|
+
/** Recorded by an envelope rule, without its text. */
|
|
16
|
+
envelope: boolean;
|
|
17
|
+
/** Its sender's mail looks like spam, a scam or a robot (the sender score). */
|
|
18
|
+
demoted: boolean;
|
|
19
|
+
}
|
|
20
|
+
/** One thread with mail after a time: `tasks agenda` and `review` read this, never the emails' text. */
|
|
21
|
+
export interface ThreadActivity {
|
|
22
|
+
/** The thread's external id: what a `chat:email/…` reference names. */
|
|
23
|
+
threadId: string;
|
|
24
|
+
subject: string | null;
|
|
25
|
+
/** ms. */
|
|
26
|
+
lastEmailAt: number;
|
|
27
|
+
emails: number;
|
|
28
|
+
/** `null` when every email of the thread is a draft. */
|
|
29
|
+
newest: NewestEmail | null;
|
|
30
|
+
}
|
|
31
|
+
/** What a reader sorts mail by, per email: never its text. */
|
|
32
|
+
export interface EmailMarks {
|
|
33
|
+
/** Filed in the mailbox of mail sent to many. */
|
|
34
|
+
bulk: boolean;
|
|
35
|
+
listId: string | null;
|
|
36
|
+
/** The mail rule that decided how it is stored, by its id in `rules list`. */
|
|
37
|
+
ruleId: number | null;
|
|
38
|
+
}
|
|
39
|
+
export interface MailActivityStore {
|
|
40
|
+
/** Each live email's marks, by its Message-ID; one the store does not hold is left out. */
|
|
41
|
+
emailMarks(accountId: number, externalIds: readonly string[]): Promise<Map<string, EmailMarks>>;
|
|
42
|
+
/**
|
|
43
|
+
* Each live thread of the account whose newest email is after `since` (ms), newest first, with the facts of that
|
|
44
|
+
* email. `where` is a `tasks agenda` query compiled over `MAIL_AGENDA_COLUMNS`.
|
|
45
|
+
*/
|
|
46
|
+
threadActivity(accountId: number, since: number, where?: Where): Promise<ThreadActivity[]>;
|
|
47
|
+
}
|
|
48
|
+
/** `tasks agenda` fields over `threadActivity`'s threads: an email thread counts as a dialog. */
|
|
49
|
+
export declare const MAIL_AGENDA_COLUMNS: {
|
|
50
|
+
readonly chat_kind: {
|
|
51
|
+
readonly type: "enum";
|
|
52
|
+
readonly sql: "'dialog'";
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
export declare const threadActivity: (database: CacheDatabase, accountId: number, since: number, where?: Where) => ThreadActivity[];
|
|
56
|
+
/** A thread's live emails, oldest first; `window` keeps those received in (after, until], in ms. */
|
|
57
|
+
export declare const threadEmailIds: (database: CacheDatabase, accountId: number, thread: string, { after, until }?: {
|
|
58
|
+
after?: number;
|
|
59
|
+
until?: number;
|
|
60
|
+
}) => number[];
|
|
61
|
+
export declare const emailMarks: (database: CacheDatabase, accountId: number, externalIds: readonly string[]) => Map<string, EmailMarks>;
|
|
62
|
+
export declare const mailActivity: (database: CacheDatabase) => MailActivityStore;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { int, str } from "./events.js";
|
|
2
|
+
/** `tasks agenda` fields over `threadActivity`'s threads: an email thread counts as a dialog. */
|
|
3
|
+
export const MAIL_AGENDA_COLUMNS = { chat_kind: { type: "enum", sql: "'dialog'" } };
|
|
4
|
+
// The thread's own time is the Date header where sync stored one, so a thread is picked by it; the newest email is
|
|
5
|
+
// picked by when it arrived, which is what an answer follows.
|
|
6
|
+
const ACTIVITY = `
|
|
7
|
+
SELECT t.external_id AS thread, t.subject, t.last_email_at, t.emails_count,
|
|
8
|
+
e.external_id, coalesce(e.received_at, e.sent_at) AS at,
|
|
9
|
+
coalesce(e.outgoing, lower(e.from_address) = lower(a.external_id)) AS outgoing,
|
|
10
|
+
EXISTS (SELECT 1 FROM email_recipients r WHERE r.email_id = e.id AND r.role IN ('to', 'cc')
|
|
11
|
+
AND lower(r.address) = lower(a.external_id)) AS addressed,
|
|
12
|
+
EXISTS (SELECT 1 FROM email_mailboxes m JOIN mailboxes b ON b.id = m.mailbox_id
|
|
13
|
+
WHERE m.email_id = e.id AND b.kind = 'bulk') AS bulk,
|
|
14
|
+
coalesce(json_extract(e.headers, '$."list-id"'), json_extract(e.metadata, '$.listId')) IS NOT NULL AS list,
|
|
15
|
+
EXISTS (SELECT 1 FROM email_mailboxes m JOIN mailboxes b ON b.id = m.mailbox_id
|
|
16
|
+
WHERE m.email_id = e.id AND b.kind = 'envelope') AS envelope,
|
|
17
|
+
EXISTS (SELECT 1 FROM account_identities ai WHERE ai.account_id = t.account_id
|
|
18
|
+
AND ai.identity_id = e.from_identity_id AND ai.mail_demoted = 1) AS demoted
|
|
19
|
+
FROM email_threads t JOIN accounts a ON a.id = t.account_id
|
|
20
|
+
LEFT JOIN emails e ON e.id = (SELECT f.id FROM emails f WHERE f.email_thread_id = t.id AND f.deleted_at IS NULL
|
|
21
|
+
AND coalesce(f.draft, 0) = 0 ORDER BY coalesce(f.received_at, f.sent_at) DESC, f.id DESC LIMIT 1)
|
|
22
|
+
WHERE t.account_id = ? AND t.deleted_at IS NULL AND t.last_email_at > ?`;
|
|
23
|
+
export const threadActivity = (database, accountId, since, where) => database
|
|
24
|
+
.prepare(`${ACTIVITY}${where ? ` AND (${where.sql})` : ""} ORDER BY t.last_email_at DESC, t.id DESC`)
|
|
25
|
+
.all(accountId, since, ...(where?.params ?? []))
|
|
26
|
+
.map((row) => ({
|
|
27
|
+
threadId: String(row.thread),
|
|
28
|
+
subject: str(row.subject),
|
|
29
|
+
lastEmailAt: Number(row.last_email_at),
|
|
30
|
+
emails: Number(row.emails_count),
|
|
31
|
+
newest: row.external_id == null
|
|
32
|
+
? null
|
|
33
|
+
: {
|
|
34
|
+
externalId: String(row.external_id),
|
|
35
|
+
at: int(row.at) ?? Number(row.last_email_at),
|
|
36
|
+
outgoing: Boolean(row.outgoing),
|
|
37
|
+
addressed: Boolean(row.addressed),
|
|
38
|
+
bulk: Boolean(row.bulk),
|
|
39
|
+
list: Boolean(row.list),
|
|
40
|
+
envelope: Boolean(row.envelope),
|
|
41
|
+
demoted: Boolean(row.demoted),
|
|
42
|
+
},
|
|
43
|
+
}));
|
|
44
|
+
/** A thread's live emails, oldest first; `window` keeps those received in (after, until], in ms. */
|
|
45
|
+
export const threadEmailIds = (database, accountId, thread, { after, until } = {}) => database
|
|
46
|
+
.prepare(`SELECT e.id FROM emails e JOIN email_threads t ON t.id = e.email_thread_id
|
|
47
|
+
WHERE t.account_id = ? AND t.external_id = ? AND e.deleted_at IS NULL
|
|
48
|
+
AND coalesce(e.received_at, e.sent_at, 0) > coalesce(?, -1)
|
|
49
|
+
AND coalesce(e.received_at, e.sent_at, 0) <= coalesce(?, 9e15)
|
|
50
|
+
ORDER BY coalesce(e.sent_at, e.received_at), e.id`)
|
|
51
|
+
.all(accountId, thread, after ?? null, until ?? null)
|
|
52
|
+
.map(({ id }) => Number(id));
|
|
53
|
+
const MARKS = `
|
|
54
|
+
SELECT e.mail_rule_id, coalesce(json_extract(e.headers, '$."list-id"'), json_extract(e.metadata, '$.listId')) AS list_id,
|
|
55
|
+
EXISTS (SELECT 1 FROM email_mailboxes m JOIN mailboxes b ON b.id = m.mailbox_id
|
|
56
|
+
WHERE m.email_id = e.id AND b.kind = 'bulk') AS bulk
|
|
57
|
+
FROM emails e WHERE e.account_id = ? AND e.external_id = ? AND e.deleted_at IS NULL`;
|
|
58
|
+
export const emailMarks = (database, accountId, externalIds) => {
|
|
59
|
+
const one = database.prepare(MARKS);
|
|
60
|
+
const marks = new Map();
|
|
61
|
+
for (const id of externalIds) {
|
|
62
|
+
const row = one.get(accountId, id);
|
|
63
|
+
if (row)
|
|
64
|
+
marks.set(id, { bulk: Boolean(row.bulk), listId: str(row.list_id), ruleId: int(row.mail_rule_id) });
|
|
65
|
+
}
|
|
66
|
+
return marks;
|
|
67
|
+
};
|
|
68
|
+
export const mailActivity = (database) => ({
|
|
69
|
+
emailMarks: async (accountId, externalIds) => emailMarks(database, accountId, externalIds),
|
|
70
|
+
threadActivity: async (accountId, since, where) => threadActivity(database, accountId, since, where),
|
|
71
|
+
});
|
|
@@ -59,3 +59,4 @@ export declare const partialMail: (database: CacheDatabase) => PartialMailStore;
|
|
|
59
59
|
export declare const storedWhole: (database: CacheDatabase, emailId: number) => boolean;
|
|
60
60
|
export declare const setLocations: (database: CacheDatabase, accountId: number, folder: string, uidvalidity: string, found: ReadonlyMap<string, number[]>) => number;
|
|
61
61
|
export declare const forgetLocations: (database: CacheDatabase, accountId: number, folder: string, uidvalidity: string, uids: number[]) => void;
|
|
62
|
+
export declare const backfillSentAt: (database: CacheDatabase, accountId: number, limit: number) => number;
|
|
@@ -107,3 +107,7 @@ export const forgetLocations = (database, accountId, folder, uidvalidity, uids)
|
|
|
107
107
|
for (const emailId of new Set(emails))
|
|
108
108
|
leave.run(emailId, Number(box.id), emailId, Number(box.id));
|
|
109
109
|
};
|
|
110
|
+
export const backfillSentAt = (database, accountId, limit) => Number(database
|
|
111
|
+
.prepare(`UPDATE emails SET sent_at = received_at WHERE id IN (
|
|
112
|
+
SELECT id FROM emails WHERE account_id = ? AND sent_at IS NULL AND received_at IS NOT NULL LIMIT ?)`)
|
|
113
|
+
.run(accountId, limit).changes);
|
|
@@ -3,6 +3,7 @@ import type { Attachment, Metadata } from "../../meetings/index.js";
|
|
|
3
3
|
import type { Stemmer, Stemmers } from "../../search/stem.js";
|
|
4
4
|
import type { CacheDatabase } from "../driver.js";
|
|
5
5
|
import type { ConversationSummary, StoredHit } from "../types.js";
|
|
6
|
+
import { type MailActivityStore } from "./email-activity.js";
|
|
6
7
|
import { type EmailLocationInput, type FolderEmail, type PartialMailStore } from "./email-locations.js";
|
|
7
8
|
import { type MailRulesStore } from "./mail-rules.js";
|
|
8
9
|
import { type AttachmentInput } from "./meetings.js";
|
|
@@ -205,7 +206,7 @@ export interface SenderScore {
|
|
|
205
206
|
demoted?: boolean;
|
|
206
207
|
}
|
|
207
208
|
/** Mail in tables of its own: threads, emails, recipients and mailboxes, keyed by account and Message-ID. */
|
|
208
|
-
export interface MailStore extends PartialMailStore {
|
|
209
|
+
export interface MailStore extends PartialMailStore, MailActivityStore {
|
|
209
210
|
saveThread(input: ThreadSave): Promise<ThreadDetails>;
|
|
210
211
|
/** Several threads and sync state in one transaction: a slice of mail commits with its resume point, or not at all. */
|
|
211
212
|
saveThreads(inputs: ThreadSave[], states?: SyncStateSave[]): Promise<ThreadDetails[]>;
|
|
@@ -238,6 +239,8 @@ export interface MailStore extends PartialMailStore {
|
|
|
238
239
|
read?: boolean;
|
|
239
240
|
flagged?: boolean;
|
|
240
241
|
}>, now: number): Promise<number>;
|
|
242
|
+
/** Gives up to `limit` emails stored without `sent_at` their internal date; answers how many. */
|
|
243
|
+
backfillSentAt(accountId: number, limit: number): Promise<number>;
|
|
241
244
|
/** The live emails whose location in `folder` has this UIDVALIDITY, oldest UID first. */
|
|
242
245
|
folderEmails(accountId: number, folder: string, uidvalidity: string): Promise<FolderEmail[]>;
|
|
243
246
|
/**
|
|
@@ -266,8 +269,11 @@ export interface MailStore extends PartialMailStore {
|
|
|
266
269
|
}[]>;
|
|
267
270
|
/** Live emails of one account by Message-ID, each placed in its thread; an unknown one is left out. */
|
|
268
271
|
inThreads(accountId: number, externalIds: string[]): Promise<EmailInThread[]>;
|
|
269
|
-
/** A thread's live emails, oldest first, as search hits
|
|
270
|
-
threadHits(accountId: number,
|
|
272
|
+
/** A thread's live emails, oldest first, as search hits; `window` keeps those received in (after, until], in ms. */
|
|
273
|
+
threadHits(accountId: number, thread: string, window?: {
|
|
274
|
+
after?: number;
|
|
275
|
+
until?: number;
|
|
276
|
+
}): Promise<StoredHit[]>;
|
|
271
277
|
/** The emails of one account nearest in meaning to `query`, each by its best chunk, best first. */
|
|
272
278
|
nearest(model: string, query: Float32Array, options: {
|
|
273
279
|
accountId: number;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { formatLocator } from "../../domain/locator.js";
|
|
2
2
|
import { formatReference } from "../../domain/references.js";
|
|
3
3
|
import { fold, normalize } from "../normalize.js";
|
|
4
|
+
import { mailActivity, threadEmailIds } from "./email-activity.js";
|
|
4
5
|
import { chunkAttachments, drainEmailIndex, dropAttachmentChunks, MAIL_PIECES, queueThreads, threadPieces, titleOf, } from "./email-index.js";
|
|
5
|
-
import { folderEmails, forgetLocations, locate, partialMail, setLocations, storedWhole, } from "./email-locations.js";
|
|
6
|
+
import { backfillSentAt, folderEmails, forgetLocations, locate, partialMail, setLocations, storedWhole, } from "./email-locations.js";
|
|
6
7
|
import { markEmailsDeleted, markEmailsRestored, recountEmailThread as recount, setEmailFlags } from "./email-state.js";
|
|
7
8
|
import { bool, flag, fromJson, int, page, str, toJson } from "./events.js";
|
|
8
9
|
import { folded } from "./folded.js";
|
|
@@ -472,6 +473,7 @@ export const mailStoreOver = ({ database, orm, now }, stemmerFor = stemmerCache(
|
|
|
472
473
|
}));
|
|
473
474
|
},
|
|
474
475
|
...partialMail(database),
|
|
476
|
+
...mailActivity(database),
|
|
475
477
|
async emailFacts(filter = {}) {
|
|
476
478
|
const [where, params] = emailWhere(filter);
|
|
477
479
|
return database
|
|
@@ -554,11 +556,8 @@ export const mailStoreOver = ({ database, orm, now }, stemmerFor = stemmerCache(
|
|
|
554
556
|
return count;
|
|
555
557
|
});
|
|
556
558
|
},
|
|
557
|
-
async
|
|
558
|
-
|
|
559
|
-
return setEmailFlags(database, accountId, flags, now);
|
|
560
|
-
});
|
|
561
|
-
},
|
|
559
|
+
setFlags: async (accountId, flags, now) => write(database, () => setEmailFlags(database, accountId, flags, now)),
|
|
560
|
+
backfillSentAt: async (accountId, limit) => write(database, () => backfillSentAt(database, accountId, limit)),
|
|
562
561
|
folderEmails: async (accountId, folder, uidvalidity) => folderEmails(database, accountId, folder, uidvalidity),
|
|
563
562
|
setLocations: async (accountId, folder, uidvalidity, found) => write(database, () => setLocations(database, accountId, folder, uidvalidity, found)),
|
|
564
563
|
forgetLocations: async (accountId, folder, uidvalidity, uids) => write(database, () => forgetLocations(database, accountId, folder, uidvalidity, uids)),
|
|
@@ -634,14 +633,8 @@ export const mailStoreOver = ({ database, orm, now }, stemmerFor = stemmerCache(
|
|
|
634
633
|
return place ? [{ ...place, score }] : [];
|
|
635
634
|
});
|
|
636
635
|
},
|
|
637
|
-
async threadHits(accountId,
|
|
638
|
-
|
|
639
|
-
.prepare(`SELECT e.id FROM emails e JOIN email_threads t ON t.id = e.email_thread_id
|
|
640
|
-
WHERE t.account_id = ? AND t.external_id = ? AND e.deleted_at IS NULL
|
|
641
|
-
ORDER BY coalesce(e.sent_at, e.received_at), e.id`)
|
|
642
|
-
.all(accountId, threadExternalId)
|
|
643
|
-
.map(({ id }) => Number(id));
|
|
644
|
-
return emailHitsByPk(database, ids);
|
|
636
|
+
async threadHits(accountId, thread, window) {
|
|
637
|
+
return emailHitsByPk(database, threadEmailIds(database, accountId, thread, window));
|
|
645
638
|
},
|
|
646
639
|
async inThreads(accountId, externalIds) {
|
|
647
640
|
const id = database.prepare("SELECT id FROM emails WHERE account_id = ? AND external_id = ? AND deleted_at IS NULL");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wirecat/cli-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.555.0",
|
|
4
4
|
"description": "The messenger-neutral half of a messaging CLI: domain model, message locators, rendering, name resolution, the SQLite seam, the command skeleton and shared read commands, the send guard, run records and the message store",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Viacheslav Ptsarev",
|