@bobfrankston/mailx-types 0.1.11 → 0.1.13

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/index.d.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  * This is the contract between client and server.
5
5
  */
6
6
  export { CONTACT_RULES } from "./contact-rules.js";
7
+ export type { MailxApi } from "./mailx-api.js";
7
8
  export { expandRecipients, splitRecipients, isAddressToken, extractAddress, } from "./groups.js";
8
9
  export type { GroupMap, RecipientToken, ExpansionResult } from "./groups.js";
9
10
  /** Supported authentication methods */
@@ -64,6 +65,30 @@ export interface Folder {
64
65
  unreadCount: number;
65
66
  children: Folder[]; /** Nested subfolders */
66
67
  }
68
+ interface FlagBearing {
69
+ flags: string[];
70
+ }
71
+ export declare const seenOf: (m: {
72
+ flags?: readonly string[];
73
+ }) => boolean;
74
+ export declare const flaggedOf: (m: {
75
+ flags?: readonly string[];
76
+ }) => boolean;
77
+ export declare const answeredOf: (m: {
78
+ flags?: readonly string[];
79
+ }) => boolean;
80
+ export declare const draftOf: (m: {
81
+ flags?: readonly string[];
82
+ }) => boolean;
83
+ export declare const deletedOf: (m: {
84
+ flags?: readonly string[];
85
+ }) => boolean;
86
+ export declare const setSeen: (m: FlagBearing, state: boolean) => void;
87
+ export declare const setFlagged: (m: FlagBearing, state: boolean) => void;
88
+ export declare const setAnswered: (m: FlagBearing, state: boolean) => void;
89
+ export declare const setDraft: (m: FlagBearing, state: boolean) => void;
90
+ export declare const toggleSeen: (m: FlagBearing) => void;
91
+ export declare const toggleFlagged: (m: FlagBearing) => void;
67
92
  /** Email address with optional display name */
68
93
  export interface EmailAddress {
69
94
  name: string; /** Display name, may be empty */
package/index.js CHANGED
@@ -12,6 +12,50 @@ export { CONTACT_RULES } from "./contact-rules.js";
12
12
  // send time. Both desktop and Android send paths consume this expander
13
13
  // against contacts.jsonc → groups.
14
14
  export { expandRecipients, splitRecipients, isAddressToken, extractAddress, } from "./groups.js";
15
+ // ── Message flag state ──
16
+ //
17
+ // External API surface for the IMAP system flags. The literal strings
18
+ // (`"\\Seen"`, `"\\Flagged"`, etc.) live ONLY inside this module — every
19
+ // caller speaks in terms of named predicates (`seenOf(msg)`) and verbs
20
+ // (`setSeen(msg, true)`, `toggleSeen(msg)`). That keeps a typo like
21
+ // `"\Seen"` (single backslash) from silently bypassing flag checks
22
+ // elsewhere, and removes the "two-API leak" of having both `FLAG.SEEN`
23
+ // constants AND verb helpers exposed simultaneously.
24
+ //
25
+ // These work against any object with a mutable `flags: string[]`
26
+ // property — `MessageEnvelope`, `Message`, and the row's `msg` reference
27
+ // all qualify. The verbs return `void` and mutate `msg.flags` in place
28
+ // (replacing the array with a fresh one so listeners observing
29
+ // reference identity, e.g. message-state, still re-render). Pure
30
+ // predicate calls (`seenOf`, `flaggedOf`) don't mutate.
31
+ const _SEEN = "\\Seen";
32
+ const _FLAGGED = "\\Flagged";
33
+ const _ANSWERED = "\\Answered";
34
+ const _DRAFT = "\\Draft";
35
+ const _DELETED = "\\Deleted";
36
+ function _has(msg, flag) {
37
+ return !!msg.flags && msg.flags.includes(flag);
38
+ }
39
+ function _set(msg, flag, state) {
40
+ const present = (msg.flags || []).includes(flag);
41
+ if (state === present)
42
+ return;
43
+ const next = state
44
+ ? [...(msg.flags || []), flag]
45
+ : (msg.flags || []).filter(f => f !== flag);
46
+ msg.flags = next;
47
+ }
48
+ export const seenOf = (m) => _has(m, _SEEN);
49
+ export const flaggedOf = (m) => _has(m, _FLAGGED);
50
+ export const answeredOf = (m) => _has(m, _ANSWERED);
51
+ export const draftOf = (m) => _has(m, _DRAFT);
52
+ export const deletedOf = (m) => _has(m, _DELETED);
53
+ export const setSeen = (m, state) => _set(m, _SEEN, state);
54
+ export const setFlagged = (m, state) => _set(m, _FLAGGED, state);
55
+ export const setAnswered = (m, state) => _set(m, _ANSWERED, state);
56
+ export const setDraft = (m, state) => _set(m, _DRAFT, state);
57
+ export const toggleSeen = (m) => _set(m, _SEEN, !_has(m, _SEEN));
58
+ export const toggleFlagged = (m) => _set(m, _FLAGGED, !_has(m, _FLAGGED));
15
59
  // ── Shared Utilities ──
16
60
  // Pure functions used by both desktop (mailx-service) and Android (web-service).
17
61
  // Kept here to avoid duplication — both platforms import from mailx-types.
package/mailx-api.d.ts ADDED
@@ -0,0 +1,212 @@
1
+ /**
2
+ * MailxApi — the single typed contract that every mailx service implementation
3
+ * must satisfy. Both the Node-side desktop service (`MailxService`) and the
4
+ * browser-side Android service (`WebMailxService`) declare
5
+ * `implements MailxApi`. Missing methods become build errors instead of
6
+ * silent runtime "parent bridge has no method X" failures.
7
+ *
8
+ * **Why this exists**: until now the two implementations drifted independently.
9
+ * A method added to MailxService for one platform release would be silently
10
+ * absent on the other for months. spellcheck-on-GDrive (2026-05-13) added
11
+ * `getUserDict` / `addUserDictWord` / `removeUserDictWord` to the desktop
12
+ * service; the Android service got none of them; the relay returned
13
+ * `parent bridge has no method`, the client `.catch(() => {})` swallowed the
14
+ * rejection, and the feature silently regressed on phone. With this contract
15
+ * any such addition is a compile-time failure on whichever side it's missing.
16
+ *
17
+ * **Surface**: the 83 method names dispatched by
18
+ * `mailx-service/jsonrpc.ts` plus a handful of internal helpers both
19
+ * implementations need. IPC-only names (`sendMessage`, `createCalendarEvent`,
20
+ * etc.) are listed under their dispatch names — desktop classes that today
21
+ * use a different internal name (e.g., `send`, `createCalendarEventLocal`)
22
+ * add a thin alias method so the contract holds without renaming call sites.
23
+ *
24
+ * **Optional methods**: a handful of methods require an OS popup or native
25
+ * filesystem hook and have no meaningful browser equivalent
26
+ * (`openInWord`, `closeWordEdit`, `showReminderPopup`, `openInTextEditor`,
27
+ * `openLocalPath`). They are declared `?:` so the browser service can
28
+ * legitimately omit them. The desktop service must implement them.
29
+ *
30
+ * **Types**: signatures use `any` where the shape genuinely varies across
31
+ * implementations (envelope rows, settings, AI payloads). The contract's
32
+ * job is name + arity enforcement, not deep schema typing — that lives in
33
+ * the relevant domain types (`Folder`, `AccountConfig`, etc.).
34
+ *
35
+ * **Where to add a new method**: add the method here first. Both
36
+ * implementations will fail to compile until they implement it. Don't add
37
+ * methods to a service class without the interface entry — that's how we
38
+ * got into the silent-drift hole.
39
+ */
40
+ import type { AutocompleteSettings, AutocompleteRequest, AutocompleteResponse, AiTransformRequest, AiTransformResponse } from "./index.js";
41
+ import type { Folder } from "./index.js";
42
+ export interface MailxApi {
43
+ getAccounts(): any[] | Promise<any[]>;
44
+ getFolders(accountId: string): Folder[] | Promise<Folder[]>;
45
+ getMessages(accountId: string, folderId: number, page?: number, pageSize?: number, sort?: string, sortDir?: string, search?: string, flaggedOnly?: boolean): any | Promise<any>;
46
+ getUnifiedInbox(page?: number, pageSize?: number): any | Promise<any>;
47
+ getMessage(accountId: string, uid: number, allowRemote?: boolean, folderId?: number): Promise<any>;
48
+ getThreadMessages(accountId: string, threadId: string): any | Promise<any>;
49
+ getAttachment(accountId: string, uid: number, attachmentId: number, folderId?: number): Promise<{
50
+ content: any;
51
+ contentType: string;
52
+ filename: string;
53
+ }>;
54
+ updateFlags(accountId: string, uid: number, flags: string[]): Promise<void>;
55
+ deleteMessage(accountId: string, uid: number): Promise<void>;
56
+ deleteMessages(accountId: string, uids: number[]): Promise<void>;
57
+ undeleteMessage(accountId: string, uid: number, folderId: number): Promise<void>;
58
+ moveMessage(accountId: string, uid: number, targetFolderId: number, targetAccountId?: string): Promise<void>;
59
+ moveMessages(accountId: string, uids: number[], targetFolderId: number): Promise<void>;
60
+ markAsSpamMessages(accountId: string, uids: number[]): Promise<{
61
+ targetFolderId: number;
62
+ moved: number;
63
+ }>;
64
+ recordSpamReport(accountId: string, uid: number, folderId: number): Promise<any>;
65
+ markFolderRead(folderId: number): void | Promise<void>;
66
+ createFolder(accountId: string, parentPath: string, name: string): Promise<void>;
67
+ renameFolder(accountId: string, folderId: number, newName: string): Promise<void>;
68
+ deleteFolder(accountId: string, folderId: number): Promise<void>;
69
+ moveFolderToTrash(accountId: string, folderId: number): Promise<void>;
70
+ emptyFolder(accountId: string, folderId: number): Promise<void>;
71
+ sendMessage(msg: any): Promise<void>;
72
+ saveDraft(accountId: string, subject: string, bodyHtml: string, bodyText: string, to?: string, cc?: string, previousDraftUid?: number, draftId?: string): Promise<{
73
+ draftUid: number | null;
74
+ draftId: string;
75
+ }>;
76
+ deleteDraft(accountId: string, draftUid: number, draftId?: string): Promise<void>;
77
+ getOutboxStatus(): any | Promise<any>;
78
+ listQueuedOutgoing(): any[] | Promise<any[]>;
79
+ cancelQueuedOutgoing(filePath: string): {
80
+ ok: true;
81
+ } | Promise<{
82
+ ok: true;
83
+ }>;
84
+ syncAll(): Promise<void>;
85
+ syncAccount(accountId: string): Promise<void>;
86
+ getSyncPending(): {
87
+ pending: number;
88
+ } | Promise<{
89
+ pending: number;
90
+ }>;
91
+ drainStoreSync(): Promise<void>;
92
+ reauthenticate(accountId: string): Promise<boolean>;
93
+ reauthGoogleScopes(): {
94
+ cleared: number;
95
+ } | Promise<{
96
+ cleared: number;
97
+ }>;
98
+ searchMessages(query: string, page?: number, pageSize?: number, scope?: string, accountId?: string, folderId?: number, includeTrashSpam?: boolean): Promise<any>;
99
+ searchContacts(query: string): any[] | Promise<any[]>;
100
+ hasCcHistoryTo(email: string): boolean | Promise<boolean>;
101
+ hasBccHistoryTo(email: string): boolean | Promise<boolean>;
102
+ addContact(name: string, email: string): boolean | Promise<boolean>;
103
+ listContacts(query: string, page?: number, pageSize?: number): any | Promise<any>;
104
+ upsertContact(name: string, email: string): {
105
+ ok: true;
106
+ } | Promise<{
107
+ ok: true;
108
+ }>;
109
+ deleteContact(email: string): {
110
+ ok: true;
111
+ } | Promise<{
112
+ ok: true;
113
+ }>;
114
+ addPreferredContact(entry: {
115
+ name: string;
116
+ email: string;
117
+ source?: string;
118
+ organization?: string;
119
+ }): Promise<void>;
120
+ addToDenylist(email: string): Promise<void>;
121
+ loadContactsConfig(): Promise<any>;
122
+ getPriorityLists(): {
123
+ senders: string[];
124
+ domains: string[];
125
+ } | Promise<{
126
+ senders: string[];
127
+ domains: string[];
128
+ }>;
129
+ setPrioritySender(email: string, value: boolean, name?: string): Promise<void>;
130
+ setPriorityDomain(domain: string, value: boolean): Promise<void>;
131
+ flagSenderOrDomain(type: "sender" | "domain", value: string): Promise<{
132
+ flagged: boolean;
133
+ }>;
134
+ allowRemoteContent(type: "sender" | "domain" | "recipient", value: string): Promise<void>;
135
+ getCalendarEvents(fromMs: number, toMs: number): Promise<any[]>;
136
+ createCalendarEvent(ev: any): Promise<{
137
+ uuid: string;
138
+ }>;
139
+ updateCalendarEvent(uuid: string, patch: any): Promise<{
140
+ ok: true;
141
+ }>;
142
+ deleteCalendarEvent(uuid: string): Promise<{
143
+ ok: true;
144
+ }>;
145
+ getTasks(includeCompleted?: boolean): Promise<any[]>;
146
+ createTask(t: {
147
+ title: string;
148
+ notes?: string;
149
+ dueMs?: number;
150
+ }): Promise<{
151
+ uuid: string;
152
+ }>;
153
+ updateTask(uuid: string, patch: any): Promise<{
154
+ ok: true;
155
+ }>;
156
+ deleteTask(uuid: string): Promise<{
157
+ ok: true;
158
+ }>;
159
+ getUserDict(): Promise<string[]>;
160
+ addUserDictWord(word: string): Promise<string[]>;
161
+ removeUserDictWord(word: string): Promise<string[]>;
162
+ getSettings(): any | Promise<any>;
163
+ saveSettingsData(settings: any): Promise<void>;
164
+ getDiagnostics(): any | Promise<any>;
165
+ getPrimaryAccount(feature?: string): any | Promise<any>;
166
+ setupAccount(name: string, email: string, password?: string): Promise<{
167
+ ok: boolean;
168
+ error?: string;
169
+ message?: string;
170
+ }>;
171
+ repairAccounts(): Promise<{
172
+ ok: boolean;
173
+ error?: string;
174
+ message?: string;
175
+ }>;
176
+ getAutocompleteSettings(): AutocompleteSettings | Promise<AutocompleteSettings>;
177
+ saveAutocompleteSettings(settings: AutocompleteSettings): void | Promise<void>;
178
+ autocomplete(req: AutocompleteRequest): Promise<AutocompleteResponse>;
179
+ aiTransform(req: AiTransformRequest): Promise<AiTransformResponse>;
180
+ readJsoncFile(name: string): Promise<string | null>;
181
+ writeJsoncFile(name: string, content: string): Promise<void>;
182
+ formatJsonc(content: string): Promise<string>;
183
+ readConfigHelp(name: string): Promise<string>;
184
+ unsubscribeOneClick(url: string): Promise<{
185
+ ok: boolean;
186
+ status: number;
187
+ statusText: string;
188
+ }>;
189
+ consumePendingMailto(): any | Promise<any>;
190
+ logClientEvent?(...args: any[]): void | Promise<void>;
191
+ getVersion?(): any | Promise<any>;
192
+ openInWord?(editId: string, html: string): Promise<{
193
+ ok: boolean;
194
+ path: string;
195
+ opener: string;
196
+ }>;
197
+ closeWordEdit?(editId: string): Promise<void>;
198
+ showReminderPopup?(opts: any): Promise<{
199
+ button: string;
200
+ form?: any;
201
+ }>;
202
+ openInTextEditor?(filePath: string): Promise<{
203
+ ok: boolean;
204
+ opener: string;
205
+ reason?: string;
206
+ }>;
207
+ openLocalPath?(which: "config" | "log"): Promise<{
208
+ ok: true;
209
+ path: string;
210
+ }>;
211
+ }
212
+ //# sourceMappingURL=mailx-api.d.ts.map
package/mailx-api.js ADDED
@@ -0,0 +1,41 @@
1
+ /**
2
+ * MailxApi — the single typed contract that every mailx service implementation
3
+ * must satisfy. Both the Node-side desktop service (`MailxService`) and the
4
+ * browser-side Android service (`WebMailxService`) declare
5
+ * `implements MailxApi`. Missing methods become build errors instead of
6
+ * silent runtime "parent bridge has no method X" failures.
7
+ *
8
+ * **Why this exists**: until now the two implementations drifted independently.
9
+ * A method added to MailxService for one platform release would be silently
10
+ * absent on the other for months. spellcheck-on-GDrive (2026-05-13) added
11
+ * `getUserDict` / `addUserDictWord` / `removeUserDictWord` to the desktop
12
+ * service; the Android service got none of them; the relay returned
13
+ * `parent bridge has no method`, the client `.catch(() => {})` swallowed the
14
+ * rejection, and the feature silently regressed on phone. With this contract
15
+ * any such addition is a compile-time failure on whichever side it's missing.
16
+ *
17
+ * **Surface**: the 83 method names dispatched by
18
+ * `mailx-service/jsonrpc.ts` plus a handful of internal helpers both
19
+ * implementations need. IPC-only names (`sendMessage`, `createCalendarEvent`,
20
+ * etc.) are listed under their dispatch names — desktop classes that today
21
+ * use a different internal name (e.g., `send`, `createCalendarEventLocal`)
22
+ * add a thin alias method so the contract holds without renaming call sites.
23
+ *
24
+ * **Optional methods**: a handful of methods require an OS popup or native
25
+ * filesystem hook and have no meaningful browser equivalent
26
+ * (`openInWord`, `closeWordEdit`, `showReminderPopup`, `openInTextEditor`,
27
+ * `openLocalPath`). They are declared `?:` so the browser service can
28
+ * legitimately omit them. The desktop service must implement them.
29
+ *
30
+ * **Types**: signatures use `any` where the shape genuinely varies across
31
+ * implementations (envelope rows, settings, AI payloads). The contract's
32
+ * job is name + arity enforcement, not deep schema typing — that lives in
33
+ * the relevant domain types (`Folder`, `AccountConfig`, etc.).
34
+ *
35
+ * **Where to add a new method**: add the method here first. Both
36
+ * implementations will fail to compile until they implement it. Don't add
37
+ * methods to a service class without the interface entry — that's how we
38
+ * got into the silent-drift hole.
39
+ */
40
+ export {};
41
+ //# sourceMappingURL=mailx-api.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx-types",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",