@diskd-ai/email-client-mcp 0.1.21 → 0.1.23
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/dist/config/schema.d.ts +112 -0
- package/dist/config/schema.js +30 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/domain/errors.d.ts +2 -0
- package/dist/domain/errors.js +18 -0
- package/dist/domain/errors.js.map +1 -1
- package/dist/imap/fetch.d.ts +14 -1
- package/dist/imap/fetch.js +58 -4
- package/dist/imap/fetch.js.map +1 -1
- package/dist/imap/mapper.js +12 -2
- package/dist/imap/mapper.js.map +1 -1
- package/dist/server.js +31 -3
- package/dist/server.js.map +1 -1
- package/dist/store/payloadTypes.d.ts +15 -2
- package/dist/sync/attachmentHydration.d.ts +44 -0
- package/dist/sync/attachmentHydration.js +118 -0
- package/dist/sync/attachmentHydration.js.map +1 -0
- package/dist/sync/bodyHydration.d.ts +52 -0
- package/dist/sync/bodyHydration.js +103 -0
- package/dist/sync/bodyHydration.js.map +1 -0
- package/dist/sync/sync.d.ts +14 -5
- package/dist/sync/sync.js +254 -96
- package/dist/sync/sync.js.map +1 -1
- package/dist/sync/watcher.d.ts +7 -0
- package/dist/sync/watcher.js +9 -0
- package/dist/sync/watcher.js.map +1 -1
- package/dist/tools/registry.d.ts +4 -0
- package/dist/tools/registry.js +10 -0
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/systemHydrateEmailAttachment.d.ts +26 -0
- package/dist/tools/systemHydrateEmailAttachment.js +13 -0
- package/dist/tools/systemHydrateEmailAttachment.js.map +1 -0
- package/dist/tools/systemHydrateEmailBodies.d.ts +40 -0
- package/dist/tools/systemHydrateEmailBodies.js +21 -0
- package/dist/tools/systemHydrateEmailBodies.js.map +1 -0
- package/package.json +1 -1
|
@@ -11,14 +11,18 @@ export type EmailAddress = {
|
|
|
11
11
|
readonly name: string | null;
|
|
12
12
|
readonly address: string;
|
|
13
13
|
};
|
|
14
|
+
export type BodyState = "not_loaded" | "loading" | "loaded" | "failed_retryable" | "failed_permanent";
|
|
15
|
+
export type AttachmentStorageState = "not_loaded" | "loading" | "loaded" | "failed_retryable" | "failed_permanent";
|
|
14
16
|
export type StoredAttachment = {
|
|
15
17
|
readonly filename: string;
|
|
16
18
|
readonly contentType: string;
|
|
17
19
|
readonly sizeBytes: number;
|
|
18
20
|
readonly partId: string;
|
|
19
21
|
readonly attachmentId?: string;
|
|
22
|
+
readonly storageState?: AttachmentStorageState;
|
|
20
23
|
readonly storedSizeBytes?: number;
|
|
21
24
|
readonly storedAt?: string;
|
|
25
|
+
readonly lastLoadError?: string;
|
|
22
26
|
};
|
|
23
27
|
export type StoredEmailPayload = {
|
|
24
28
|
readonly accountId: string;
|
|
@@ -41,6 +45,9 @@ export type StoredEmailPayload = {
|
|
|
41
45
|
readonly bodyText: string | null;
|
|
42
46
|
readonly bodyHtml: string | null;
|
|
43
47
|
readonly truncated: boolean;
|
|
48
|
+
readonly bodyState?: BodyState;
|
|
49
|
+
readonly bodyFetchedAt?: string | null;
|
|
50
|
+
readonly bodyFetchError?: string | null;
|
|
44
51
|
readonly fetchedAt: string;
|
|
45
52
|
};
|
|
46
53
|
/**
|
|
@@ -50,14 +57,20 @@ export type StoredEmailPayload = {
|
|
|
50
57
|
* - `uidValidity`: snapshot of last seen UIDVALIDITY; mismatch triggers
|
|
51
58
|
* a full folder drop+resync.
|
|
52
59
|
* - `uidNext`: snapshot of last seen UIDNEXT; the head of the sync window.
|
|
53
|
-
* - `
|
|
54
|
-
*
|
|
60
|
+
* - `forwardSyncedUid`: highest UID confirmed written for the new-mail
|
|
61
|
+
* forward cursor.
|
|
62
|
+
* - `backfillBeforeUid`: lowest UID covered by historical backfill. A
|
|
63
|
+
* non-null value means older messages below this UID are still pending.
|
|
64
|
+
* - `lastSyncedUid`: compatibility mirror of `forwardSyncedUid` for old
|
|
65
|
+
* metadata readers and old folder states.
|
|
55
66
|
* - `lastSyncStartedAt` / `lastSyncFinishedAt` / `lastSyncError`:
|
|
56
67
|
* diagnostic snapshot exposed via `get_watcher_status`.
|
|
57
68
|
*/
|
|
58
69
|
export type SyncState = {
|
|
59
70
|
readonly uidValidity: number;
|
|
60
71
|
readonly uidNext: number;
|
|
72
|
+
readonly forwardSyncedUid: number;
|
|
73
|
+
readonly backfillBeforeUid: number | null;
|
|
61
74
|
readonly lastSyncedUid: number;
|
|
62
75
|
readonly lastSyncStartedAt: string | null;
|
|
63
76
|
readonly lastSyncFinishedAt: string | null;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type AppError } from "../domain/errors.js";
|
|
2
|
+
import { type Result } from "../domain/result.js";
|
|
3
|
+
import { type UploadAttachmentResult } from "../store/attachments.js";
|
|
4
|
+
import type { StoredAttachment, StoredEmailPayload } from "../store/payloadTypes.js";
|
|
5
|
+
export type AttachmentHydrationRef = {
|
|
6
|
+
readonly mailboxId: string;
|
|
7
|
+
readonly folderId: string;
|
|
8
|
+
readonly externalId: string;
|
|
9
|
+
readonly attachmentId: string;
|
|
10
|
+
};
|
|
11
|
+
export type AttachmentHydrationDeps = {
|
|
12
|
+
readonly drive: {
|
|
13
|
+
readonly getMessage: (mailboxId: string, folderId: string, externalId: string) => Promise<Result<AppError, StoredEmailPayload | null>>;
|
|
14
|
+
readonly upsertMessages: (mailboxId: string, folderId: string, payloads: readonly StoredEmailPayload[], externalIds: readonly string[]) => Promise<Result<AppError, {
|
|
15
|
+
inserted: number;
|
|
16
|
+
updated: number;
|
|
17
|
+
}>>;
|
|
18
|
+
readonly uploadAttachment: (mailboxId: string, folderId: string, externalId: string, attachment: StoredAttachment & {
|
|
19
|
+
readonly attachmentId: string;
|
|
20
|
+
}, content: AsyncIterable<Uint8Array>) => Promise<Result<AppError, UploadAttachmentResult>>;
|
|
21
|
+
};
|
|
22
|
+
readonly imap: {
|
|
23
|
+
readonly downloadPart: (accountId: string, mailbox: string, uid: number, partId: string) => Promise<{
|
|
24
|
+
readonly content: AsyncIterable<Uint8Array>;
|
|
25
|
+
readonly sizeBytes: number | null;
|
|
26
|
+
readonly contentType: string | null;
|
|
27
|
+
readonly dispose: () => void;
|
|
28
|
+
}>;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
export type AttachmentHydrationStatus = "loaded" | "skipped" | "failed_retryable" | "failed_permanent";
|
|
32
|
+
export type AttachmentHydrationOutcome = {
|
|
33
|
+
readonly status: AttachmentHydrationStatus;
|
|
34
|
+
readonly mailboxId: string;
|
|
35
|
+
readonly folderId: string;
|
|
36
|
+
readonly externalId: string;
|
|
37
|
+
readonly attachmentId: string;
|
|
38
|
+
readonly attachment: StoredAttachment | null;
|
|
39
|
+
readonly payload: StoredEmailPayload;
|
|
40
|
+
readonly error: string | null;
|
|
41
|
+
};
|
|
42
|
+
export declare const hydrateStoredMessageAttachment: (deps: AttachmentHydrationDeps, ref: AttachmentHydrationRef, options?: {
|
|
43
|
+
readonly refresh?: boolean;
|
|
44
|
+
}) => Promise<Result<AppError, AttachmentHydrationOutcome>>;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { errorMessage, isRetryableImapError, notFound } from "../domain/errors.js";
|
|
2
|
+
import { Err, Ok } from "../domain/result.js";
|
|
3
|
+
import { attachmentIdFor } from "../store/attachments.js";
|
|
4
|
+
const resolvedAttachmentId = (payload, attachment) => attachment.attachmentId ?? attachmentIdFor(payload.uidValidity, payload.uid, attachment.partId);
|
|
5
|
+
const findAttachment = (payload, attachmentId) => payload.attachments.find((attachment) => resolvedAttachmentId(payload, attachment) === attachmentId) ?? null;
|
|
6
|
+
const patchAttachment = (payload, attachmentId, patch) => {
|
|
7
|
+
let patchedAttachment = null;
|
|
8
|
+
const attachments = payload.attachments.map((attachment) => {
|
|
9
|
+
const nextAttachment = {
|
|
10
|
+
...attachment,
|
|
11
|
+
attachmentId: resolvedAttachmentId(payload, attachment),
|
|
12
|
+
};
|
|
13
|
+
if (nextAttachment.attachmentId !== attachmentId)
|
|
14
|
+
return nextAttachment;
|
|
15
|
+
patchedAttachment = patch(nextAttachment);
|
|
16
|
+
return patchedAttachment;
|
|
17
|
+
});
|
|
18
|
+
return { payload: { ...payload, attachments }, attachment: patchedAttachment };
|
|
19
|
+
};
|
|
20
|
+
const patchLoaded = (payload, attachmentId, uploaded) => patchAttachment(payload, attachmentId, (attachment) => {
|
|
21
|
+
const { lastLoadError: _lastLoadError, ...rest } = attachment;
|
|
22
|
+
return {
|
|
23
|
+
...rest,
|
|
24
|
+
storageState: "loaded",
|
|
25
|
+
storedSizeBytes: uploaded.storedSizeBytes,
|
|
26
|
+
storedAt: uploaded.storedAt,
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
const patchFailed = (payload, attachmentId, status, message) => patchAttachment(payload, attachmentId, (attachment) => ({
|
|
30
|
+
...attachment,
|
|
31
|
+
storageState: status,
|
|
32
|
+
lastLoadError: message,
|
|
33
|
+
}));
|
|
34
|
+
const persist = async (deps, ref, payload) => {
|
|
35
|
+
const upsert = await deps.drive.upsertMessages(ref.mailboxId, ref.folderId, [payload], [ref.externalId]);
|
|
36
|
+
if (upsert.tag === "Err")
|
|
37
|
+
return upsert;
|
|
38
|
+
return Ok(payload);
|
|
39
|
+
};
|
|
40
|
+
const isRetryableAppFailure = (cause) => isRetryableImapError(cause);
|
|
41
|
+
const outcome = (ref, payload, status, attachment, error) => ({
|
|
42
|
+
status,
|
|
43
|
+
mailboxId: ref.mailboxId,
|
|
44
|
+
folderId: ref.folderId,
|
|
45
|
+
externalId: ref.externalId,
|
|
46
|
+
attachmentId: ref.attachmentId,
|
|
47
|
+
attachment,
|
|
48
|
+
payload,
|
|
49
|
+
error,
|
|
50
|
+
});
|
|
51
|
+
export const hydrateStoredMessageAttachment = async (deps, ref, options) => {
|
|
52
|
+
const existing = await deps.drive.getMessage(ref.mailboxId, ref.folderId, ref.externalId);
|
|
53
|
+
if (existing.tag === "Err")
|
|
54
|
+
return existing;
|
|
55
|
+
if (existing.value === null)
|
|
56
|
+
return Err(notFound(`message ${ref.externalId}`));
|
|
57
|
+
const payload = existing.value;
|
|
58
|
+
const attachment = findAttachment(payload, ref.attachmentId);
|
|
59
|
+
if (attachment === null) {
|
|
60
|
+
return Ok(outcome(ref, payload, "failed_permanent", null, `attachment not found: ${ref.attachmentId}`));
|
|
61
|
+
}
|
|
62
|
+
const attachmentWithId = {
|
|
63
|
+
...attachment,
|
|
64
|
+
attachmentId: resolvedAttachmentId(payload, attachment),
|
|
65
|
+
};
|
|
66
|
+
if (attachmentWithId.storageState === "loaded" && options?.refresh !== true) {
|
|
67
|
+
return Ok(outcome(ref, payload, "skipped", attachmentWithId, null));
|
|
68
|
+
}
|
|
69
|
+
if (attachmentWithId.partId.length === 0) {
|
|
70
|
+
const message = `attachment partId is missing: ${ref.attachmentId}`;
|
|
71
|
+
const patched = patchFailed(payload, ref.attachmentId, "failed_permanent", message);
|
|
72
|
+
const persisted = await persist(deps, ref, patched.payload);
|
|
73
|
+
if (persisted.tag === "Err")
|
|
74
|
+
return persisted;
|
|
75
|
+
return Ok(outcome(ref, persisted.value, "failed_permanent", patched.attachment, message));
|
|
76
|
+
}
|
|
77
|
+
let downloaded;
|
|
78
|
+
try {
|
|
79
|
+
downloaded = await deps.imap.downloadPart(payload.accountId, payload.mailbox, payload.uid, attachmentWithId.partId);
|
|
80
|
+
}
|
|
81
|
+
catch (cause) {
|
|
82
|
+
const message = cause?.message ?? String(cause);
|
|
83
|
+
const status = isRetryableAppFailure(cause) ? "failed_retryable" : "failed_permanent";
|
|
84
|
+
const patched = patchFailed(payload, ref.attachmentId, status, message);
|
|
85
|
+
const persisted = await persist(deps, ref, patched.payload);
|
|
86
|
+
if (persisted.tag === "Err")
|
|
87
|
+
return persisted;
|
|
88
|
+
return Ok(outcome(ref, persisted.value, status, patched.attachment, message));
|
|
89
|
+
}
|
|
90
|
+
const uploadAttachment = {
|
|
91
|
+
...attachmentWithId,
|
|
92
|
+
sizeBytes: downloaded.sizeBytes ?? attachmentWithId.sizeBytes,
|
|
93
|
+
contentType: downloaded.contentType ?? attachmentWithId.contentType,
|
|
94
|
+
};
|
|
95
|
+
const uploaded = await (async () => {
|
|
96
|
+
try {
|
|
97
|
+
return await deps.drive.uploadAttachment(ref.mailboxId, ref.folderId, ref.externalId, uploadAttachment, downloaded.content);
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
downloaded.dispose();
|
|
101
|
+
}
|
|
102
|
+
})();
|
|
103
|
+
if (uploaded.tag === "Err") {
|
|
104
|
+
const message = errorMessage(uploaded.error);
|
|
105
|
+
const status = isRetryableAppFailure(message) ? "failed_retryable" : "failed_permanent";
|
|
106
|
+
const patched = patchFailed(payload, ref.attachmentId, status, message);
|
|
107
|
+
const persisted = await persist(deps, ref, patched.payload);
|
|
108
|
+
if (persisted.tag === "Err")
|
|
109
|
+
return persisted;
|
|
110
|
+
return Ok(outcome(ref, persisted.value, status, patched.attachment, message));
|
|
111
|
+
}
|
|
112
|
+
const patched = patchLoaded(payload, ref.attachmentId, uploaded.value);
|
|
113
|
+
const persisted = await persist(deps, ref, patched.payload);
|
|
114
|
+
if (persisted.tag === "Err")
|
|
115
|
+
return persisted;
|
|
116
|
+
return Ok(outcome(ref, persisted.value, "loaded", patched.attachment, null));
|
|
117
|
+
};
|
|
118
|
+
//# sourceMappingURL=attachmentHydration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachmentHydration.js","sourceRoot":"","sources":["../../src/sync/attachmentHydration.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type AppError, type ImapError } from "../domain/errors.js";
|
|
2
|
+
import { type Result } from "../domain/result.js";
|
|
3
|
+
import type { StoredEmailPayload } from "../store/payloadTypes.js";
|
|
4
|
+
export type FetchedBodyContent = {
|
|
5
|
+
readonly bodyText: string | null;
|
|
6
|
+
readonly bodyHtml: string | null;
|
|
7
|
+
readonly truncated: boolean;
|
|
8
|
+
readonly bytesRead: number;
|
|
9
|
+
};
|
|
10
|
+
export type BodyHydrationRef = {
|
|
11
|
+
readonly mailboxId: string;
|
|
12
|
+
readonly folderId: string;
|
|
13
|
+
readonly externalId: string;
|
|
14
|
+
};
|
|
15
|
+
export type BodyHydrationDeps = {
|
|
16
|
+
readonly drive: {
|
|
17
|
+
readonly getMessage: (mailboxId: string, folderId: string, externalId: string) => Promise<Result<AppError, StoredEmailPayload | null>>;
|
|
18
|
+
readonly upsertMessages: (mailboxId: string, folderId: string, payloads: readonly StoredEmailPayload[], externalIds: readonly string[]) => Promise<Result<AppError, {
|
|
19
|
+
inserted: number;
|
|
20
|
+
updated: number;
|
|
21
|
+
}>>;
|
|
22
|
+
};
|
|
23
|
+
readonly imap: {
|
|
24
|
+
readonly fetchBody: (accountId: string, mailbox: string, uid: number) => Promise<Result<ImapError, FetchedBodyContent | null>>;
|
|
25
|
+
};
|
|
26
|
+
readonly now: () => Date;
|
|
27
|
+
};
|
|
28
|
+
export type BodyHydrationStatus = "loaded" | "skipped" | "failed_retryable" | "failed_permanent";
|
|
29
|
+
export type BodyHydrationOutcome = {
|
|
30
|
+
readonly status: BodyHydrationStatus;
|
|
31
|
+
readonly payload: StoredEmailPayload;
|
|
32
|
+
readonly error: string | null;
|
|
33
|
+
};
|
|
34
|
+
export type BodyHydrationBatchItem = BodyHydrationOutcome & {
|
|
35
|
+
readonly ref: BodyHydrationRef;
|
|
36
|
+
};
|
|
37
|
+
export type BodyHydrationBatchResult = {
|
|
38
|
+
readonly loaded: readonly BodyHydrationBatchItem[];
|
|
39
|
+
readonly skipped: readonly BodyHydrationBatchItem[];
|
|
40
|
+
readonly failedRetryable: readonly BodyHydrationBatchItem[];
|
|
41
|
+
readonly failedPermanent: readonly BodyHydrationBatchItem[];
|
|
42
|
+
};
|
|
43
|
+
export declare const markBodyLoaded: (payload: StoredEmailPayload, body: FetchedBodyContent, now: Date) => StoredEmailPayload;
|
|
44
|
+
export declare const markBodyFailedRetryable: (payload: StoredEmailPayload, error: string, now: Date) => StoredEmailPayload;
|
|
45
|
+
export declare const markBodyFailedPermanent: (payload: StoredEmailPayload, error: string, now: Date) => StoredEmailPayload;
|
|
46
|
+
export declare const hydrateStoredMessageBody: (deps: BodyHydrationDeps, ref: BodyHydrationRef, options?: {
|
|
47
|
+
readonly refresh?: boolean;
|
|
48
|
+
}) => Promise<Result<AppError, BodyHydrationOutcome>>;
|
|
49
|
+
export declare const hydrateStoredMessageBodies: (deps: BodyHydrationDeps, refs: readonly BodyHydrationRef[], options?: {
|
|
50
|
+
readonly refresh?: boolean;
|
|
51
|
+
readonly maxMessages?: number;
|
|
52
|
+
}) => Promise<Result<AppError, BodyHydrationBatchResult>>;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { errorMessage, isRetryableImapError, notFound, } from "../domain/errors.js";
|
|
2
|
+
import { Err, Ok } from "../domain/result.js";
|
|
3
|
+
import { htmlToText } from "../imap/mapper.js";
|
|
4
|
+
const buildSnippet = (bodyText, bodyHtml) => {
|
|
5
|
+
const text = bodyText ?? (bodyHtml !== null ? htmlToText(bodyHtml) : null);
|
|
6
|
+
if (text === null)
|
|
7
|
+
return "";
|
|
8
|
+
return text.replace(/\s+/g, " ").trim().slice(0, 200);
|
|
9
|
+
};
|
|
10
|
+
const messageHasLoadedBody = (payload) => payload.bodyState === "loaded" ||
|
|
11
|
+
(payload.bodyState === undefined && (payload.bodyText !== null || payload.bodyHtml !== null));
|
|
12
|
+
export const markBodyLoaded = (payload, body, now) => ({
|
|
13
|
+
...payload,
|
|
14
|
+
snippet: buildSnippet(body.bodyText, body.bodyHtml),
|
|
15
|
+
bodyText: body.bodyText,
|
|
16
|
+
bodyHtml: body.bodyHtml,
|
|
17
|
+
truncated: payload.truncated || body.truncated,
|
|
18
|
+
bodyState: "loaded",
|
|
19
|
+
bodyFetchedAt: now.toISOString(),
|
|
20
|
+
bodyFetchError: null,
|
|
21
|
+
});
|
|
22
|
+
export const markBodyFailedRetryable = (payload, error, now) => ({
|
|
23
|
+
...payload,
|
|
24
|
+
bodyState: "failed_retryable",
|
|
25
|
+
bodyFetchedAt: now.toISOString(),
|
|
26
|
+
bodyFetchError: error,
|
|
27
|
+
});
|
|
28
|
+
export const markBodyFailedPermanent = (payload, error, now) => ({
|
|
29
|
+
...payload,
|
|
30
|
+
bodyState: "failed_permanent",
|
|
31
|
+
bodyFetchedAt: now.toISOString(),
|
|
32
|
+
bodyFetchError: error,
|
|
33
|
+
});
|
|
34
|
+
const persistBodyState = async (deps, ref, payload) => {
|
|
35
|
+
const upsert = await deps.drive.upsertMessages(ref.mailboxId, ref.folderId, [payload], [ref.externalId]);
|
|
36
|
+
if (upsert.tag === "Err")
|
|
37
|
+
return upsert;
|
|
38
|
+
return Ok(payload);
|
|
39
|
+
};
|
|
40
|
+
const imapFailureIsRetryable = (error) => isRetryableImapError(error.message) || isRetryableImapError(error.cause);
|
|
41
|
+
export const hydrateStoredMessageBody = async (deps, ref, options) => {
|
|
42
|
+
const existing = await deps.drive.getMessage(ref.mailboxId, ref.folderId, ref.externalId);
|
|
43
|
+
if (existing.tag === "Err")
|
|
44
|
+
return existing;
|
|
45
|
+
if (existing.value === null)
|
|
46
|
+
return Err(notFound(`message ${ref.externalId}`));
|
|
47
|
+
const payload = existing.value;
|
|
48
|
+
if (options?.refresh !== true && messageHasLoadedBody(payload)) {
|
|
49
|
+
return Ok({ status: "skipped", payload, error: null });
|
|
50
|
+
}
|
|
51
|
+
const fetched = await deps.imap.fetchBody(payload.accountId, payload.mailbox, payload.uid);
|
|
52
|
+
if (fetched.tag === "Err") {
|
|
53
|
+
const message = errorMessage(fetched.error);
|
|
54
|
+
const patched = imapFailureIsRetryable(fetched.error)
|
|
55
|
+
? markBodyFailedRetryable(payload, message, deps.now())
|
|
56
|
+
: markBodyFailedPermanent(payload, message, deps.now());
|
|
57
|
+
const persisted = await persistBodyState(deps, ref, patched);
|
|
58
|
+
if (persisted.tag === "Err")
|
|
59
|
+
return persisted;
|
|
60
|
+
return Ok({
|
|
61
|
+
status: imapFailureIsRetryable(fetched.error) ? "failed_retryable" : "failed_permanent",
|
|
62
|
+
payload: persisted.value,
|
|
63
|
+
error: message,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
if (fetched.value === null) {
|
|
67
|
+
const message = `message body not found: ${ref.externalId}`;
|
|
68
|
+
const patched = markBodyFailedPermanent(payload, message, deps.now());
|
|
69
|
+
const persisted = await persistBodyState(deps, ref, patched);
|
|
70
|
+
if (persisted.tag === "Err")
|
|
71
|
+
return persisted;
|
|
72
|
+
return Ok({ status: "failed_permanent", payload: persisted.value, error: message });
|
|
73
|
+
}
|
|
74
|
+
const patched = markBodyLoaded(payload, fetched.value, deps.now());
|
|
75
|
+
const persisted = await persistBodyState(deps, ref, patched);
|
|
76
|
+
if (persisted.tag === "Err")
|
|
77
|
+
return persisted;
|
|
78
|
+
return Ok({ status: "loaded", payload: persisted.value, error: null });
|
|
79
|
+
};
|
|
80
|
+
export const hydrateStoredMessageBodies = async (deps, refs, options) => {
|
|
81
|
+
const maxMessages = options?.maxMessages ?? refs.length;
|
|
82
|
+
const selectedRefs = refs.slice(0, Math.max(0, maxMessages));
|
|
83
|
+
const loaded = [];
|
|
84
|
+
const skipped = [];
|
|
85
|
+
const failedRetryable = [];
|
|
86
|
+
const failedPermanent = [];
|
|
87
|
+
for (const ref of selectedRefs) {
|
|
88
|
+
const result = await hydrateStoredMessageBody(deps, ref, { refresh: options?.refresh });
|
|
89
|
+
if (result.tag === "Err")
|
|
90
|
+
return result;
|
|
91
|
+
const item = { ...result.value, ref };
|
|
92
|
+
if (item.status === "loaded")
|
|
93
|
+
loaded.push(item);
|
|
94
|
+
else if (item.status === "skipped")
|
|
95
|
+
skipped.push(item);
|
|
96
|
+
else if (item.status === "failed_retryable")
|
|
97
|
+
failedRetryable.push(item);
|
|
98
|
+
else
|
|
99
|
+
failedPermanent.push(item);
|
|
100
|
+
}
|
|
101
|
+
return Ok({ loaded, skipped, failedRetryable, failedPermanent });
|
|
102
|
+
};
|
|
103
|
+
//# sourceMappingURL=bodyHydration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bodyHydration.js","sourceRoot":"","sources":["../../src/sync/bodyHydration.ts"],"names":[],"mappings":"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"}
|
package/dist/sync/sync.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ import { type Account, type WatcherSettings } from "../config/schema.js";
|
|
|
21
21
|
import { type AppError, type ImapError } from "../domain/errors.js";
|
|
22
22
|
import { type Result } from "../domain/result.js";
|
|
23
23
|
import { type FetchedMessageLike } from "../imap/mapper.js";
|
|
24
|
-
import {
|
|
24
|
+
import type { UploadAttachmentResult } from "../store/attachments.js";
|
|
25
25
|
import type { StoredAttachment, StoredEmailPayload, SyncState } from "../store/payloadTypes.js";
|
|
26
26
|
export type SyncDeps = {
|
|
27
27
|
readonly drive: {
|
|
@@ -56,12 +56,14 @@ export type SyncDeps = {
|
|
|
56
56
|
uidNext: number;
|
|
57
57
|
messages: number;
|
|
58
58
|
}>>;
|
|
59
|
-
readonly
|
|
60
|
-
|
|
59
|
+
readonly fetchMetadataRange: (accountId: string, path: string, fromUid: number, toUid: number) => AsyncIterable<FetchedMessageLike>;
|
|
60
|
+
readonly fetchEnvelopesRange: (accountId: string, path: string, fromUid: number, toUid: number) => AsyncIterable<FetchedMessageLike>;
|
|
61
|
+
readonly fetchBody: (accountId: string, mailbox: string, uid: number) => Promise<Result<ImapError, {
|
|
61
62
|
readonly bodyText: string | null;
|
|
62
63
|
readonly bodyHtml: string | null;
|
|
63
|
-
|
|
64
|
-
|
|
64
|
+
readonly truncated: boolean;
|
|
65
|
+
readonly bytesRead: number;
|
|
66
|
+
} | null>>;
|
|
65
67
|
readonly downloadPart: (accountId: string, path: string, uid: number, partId: string) => Promise<{
|
|
66
68
|
readonly content: AsyncIterable<Uint8Array>;
|
|
67
69
|
readonly sizeBytes: number | null;
|
|
@@ -74,8 +76,15 @@ export type SyncDeps = {
|
|
|
74
76
|
export type SyncFolderReport = {
|
|
75
77
|
readonly folderId: string;
|
|
76
78
|
readonly newMessages: number;
|
|
79
|
+
readonly forwardMessages?: number;
|
|
80
|
+
readonly backfilledMessages?: number;
|
|
77
81
|
readonly reconciledFlags: number;
|
|
82
|
+
readonly hydratedBodies: number;
|
|
83
|
+
readonly bodyHydrationErrors: number;
|
|
78
84
|
readonly uidValidityRolled: boolean;
|
|
85
|
+
readonly forwardSyncedUid?: number | null;
|
|
86
|
+
readonly backfillBeforeUid?: number | null;
|
|
87
|
+
readonly backfillComplete?: boolean;
|
|
79
88
|
readonly error: string | null;
|
|
80
89
|
};
|
|
81
90
|
export type SyncReport = {
|