@wirecat/cli-core 0.554.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 +20 -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/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/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/review.d.ts +37 -0
- package/dist/mail/review.js +118 -0
- 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/emails.d.ts +7 -3
- package/dist/store/sqlite/emails.js +4 -8
- package/package.json +1 -1
|
@@ -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
|
+
});
|
|
@@ -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[]>;
|
|
@@ -268,8 +269,11 @@ export interface MailStore extends PartialMailStore {
|
|
|
268
269
|
}[]>;
|
|
269
270
|
/** Live emails of one account by Message-ID, each placed in its thread; an unknown one is left out. */
|
|
270
271
|
inThreads(accountId: number, externalIds: string[]): Promise<EmailInThread[]>;
|
|
271
|
-
/** A thread's live emails, oldest first, as search hits
|
|
272
|
-
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[]>;
|
|
273
277
|
/** The emails of one account nearest in meaning to `query`, each by its best chunk, best first. */
|
|
274
278
|
nearest(model: string, query: Float32Array, options: {
|
|
275
279
|
accountId: number;
|
|
@@ -1,6 +1,7 @@
|
|
|
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
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";
|
|
@@ -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
|
|
@@ -631,14 +633,8 @@ export const mailStoreOver = ({ database, orm, now }, stemmerFor = stemmerCache(
|
|
|
631
633
|
return place ? [{ ...place, score }] : [];
|
|
632
634
|
});
|
|
633
635
|
},
|
|
634
|
-
async threadHits(accountId,
|
|
635
|
-
|
|
636
|
-
.prepare(`SELECT e.id FROM emails e JOIN email_threads t ON t.id = e.email_thread_id
|
|
637
|
-
WHERE t.account_id = ? AND t.external_id = ? AND e.deleted_at IS NULL
|
|
638
|
-
ORDER BY coalesce(e.sent_at, e.received_at), e.id`)
|
|
639
|
-
.all(accountId, threadExternalId)
|
|
640
|
-
.map(({ id }) => Number(id));
|
|
641
|
-
return emailHitsByPk(database, ids);
|
|
636
|
+
async threadHits(accountId, thread, window) {
|
|
637
|
+
return emailHitsByPk(database, threadEmailIds(database, accountId, thread, window));
|
|
642
638
|
},
|
|
643
639
|
async inThreads(accountId, externalIds) {
|
|
644
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",
|