@bobfrankston/rmfmail 1.1.3 → 1.1.5
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/bin/build-bundles.mjs +14 -4
- package/bin/mailx.js +15 -21
- package/bin/mailx.js.map +1 -1
- package/bin/mailx.ts +16 -22
- package/client/android-bootstrap.bundle.js +2151 -2
- package/client/android-bootstrap.bundle.js.map +4 -4
- package/client/app.bundle.js +30 -16
- package/client/app.bundle.js.map +2 -2
- package/client/app.js +20 -20
- package/client/app.js.map +1 -1
- package/client/app.ts +20 -20
- package/client/components/context-menu.js +9 -0
- package/client/components/context-menu.js.map +1 -1
- package/client/components/context-menu.ts +8 -0
- package/client/components/message-viewer.js +21 -5
- package/client/components/message-viewer.js.map +1 -1
- package/client/components/message-viewer.ts +21 -5
- package/client/compose/compose.bundle.js +4 -0
- package/client/compose/compose.bundle.js.map +2 -2
- package/client/index.html +23 -4
- package/client/lib/mailxapi.js +11 -7
- package/package.json +9 -9
- package/packages/mailx-api/index.d.ts +2 -2
- package/packages/mailx-api/index.d.ts.map +1 -1
- package/packages/mailx-api/index.js +2 -2
- package/packages/mailx-api/index.js.map +1 -1
- package/packages/mailx-api/index.ts +3 -3
- package/packages/mailx-core/index.d.ts.map +1 -1
- package/packages/mailx-core/index.js +3 -2
- package/packages/mailx-core/index.js.map +1 -1
- package/packages/mailx-core/index.ts +3 -2
- package/packages/mailx-imap/index.d.ts +13 -4
- package/packages/mailx-imap/index.d.ts.map +1 -1
- package/packages/mailx-imap/index.js +16 -8
- package/packages/mailx-imap/index.js.map +1 -1
- package/packages/mailx-imap/index.ts +15 -7
- package/packages/mailx-imap/package-lock.json +2 -2
- package/packages/mailx-imap/package.json +1 -1
- package/packages/mailx-server/index.d.ts.map +1 -1
- package/packages/mailx-server/index.js +4 -3
- package/packages/mailx-server/index.js.map +1 -1
- package/packages/mailx-server/index.ts +4 -3
- package/packages/mailx-service/db-worker.js +3 -4
- package/packages/mailx-service/db-worker.js.map +1 -1
- package/packages/mailx-service/db-worker.ts +5 -6
- package/packages/mailx-service/index.d.ts +20 -3
- package/packages/mailx-service/index.d.ts.map +1 -1
- package/packages/mailx-service/index.js +19 -17
- package/packages/mailx-service/index.js.map +1 -1
- package/packages/mailx-service/index.ts +18 -17
- package/packages/mailx-service/local-store.d.ts +7 -144
- package/packages/mailx-service/local-store.d.ts.map +1 -1
- package/packages/mailx-service/local-store.js +6 -511
- package/packages/mailx-service/local-store.js.map +1 -1
- package/packages/mailx-service/local-store.ts +7 -551
- package/packages/mailx-store/charset.d.ts +15 -0
- package/packages/mailx-store/charset.d.ts.map +1 -0
- package/packages/mailx-store/charset.js +61 -0
- package/packages/mailx-store/charset.js.map +1 -0
- package/packages/mailx-store/charset.ts +45 -0
- package/packages/mailx-store/index.d.ts +2 -0
- package/packages/mailx-store/index.d.ts.map +1 -1
- package/packages/mailx-store/index.js +2 -0
- package/packages/mailx-store/index.js.map +1 -1
- package/packages/mailx-store/index.ts +4 -0
- package/packages/mailx-store/package.json +1 -1
- package/packages/mailx-store/store.d.ts +169 -0
- package/packages/mailx-store/store.d.ts.map +1 -0
- package/packages/mailx-store/store.js +528 -0
- package/packages/mailx-store/store.js.map +1 -0
- package/packages/mailx-store/store.ts +567 -0
- /package/packages/mailx-imap/{node_modules.npmglobalize-stash-444 → node_modules.npmglobalize-stash-11408}/.package-lock.json +0 -0
|
@@ -13,7 +13,7 @@ const __dirname = import.meta.dirname;
|
|
|
13
13
|
import { ImapManager } from "@bobfrankston/mailx-imap";
|
|
14
14
|
import * as gsync from "./google-sync.js";
|
|
15
15
|
import { sniffAndFixCharset } from "./charset.js";
|
|
16
|
-
import {
|
|
16
|
+
import { Store } from "@bobfrankston/mailx-store";
|
|
17
17
|
import { SyncQueue } from "./sync-queue.js";
|
|
18
18
|
import { Reconciler } from "./reconciler.js";
|
|
19
19
|
import { loadSettings, saveSettings, loadAccounts, loadAccountsAsync, saveAccounts, initCloudConfig, loadAllowlist, saveAllowlist, loadAutocomplete, saveAutocomplete, loadKeys, saveKeys, ensureKeysSectionExists, getStorePath, getStorageInfo, getConfigDir, loadUserDict, saveUserDict } from "@bobfrankston/mailx-settings";
|
|
@@ -135,7 +135,7 @@ export class MailxService implements MailxApi {
|
|
|
135
135
|
/** Local-first read/write facade. Every UI IPC handler that touches the
|
|
136
136
|
* local DB or body store goes through this — no awaiting IMAP, no
|
|
137
137
|
* awaiting Gmail API, no awaiting SMTP. See docs/local-first-plan.md. */
|
|
138
|
-
private localStore:
|
|
138
|
+
private localStore: Store;
|
|
139
139
|
|
|
140
140
|
/** Persistent (and in-memory body-fetch) queue. UI handlers commit
|
|
141
141
|
* locally, then enqueue a server-mirror task here. */
|
|
@@ -146,20 +146,21 @@ export class MailxService implements MailxApi {
|
|
|
146
146
|
private reconciler: Reconciler;
|
|
147
147
|
|
|
148
148
|
constructor(
|
|
149
|
-
|
|
149
|
+
/** The Store is the nexus — owns DB + .eml files + bus + operations.
|
|
150
|
+
* MailxService is the IPC adapter on top: it routes UI requests to
|
|
151
|
+
* Store reads/writes and to the SyncQueue/Reconciler for server-
|
|
152
|
+
* mirror work. The raw `db` getter below is a transitional shim so
|
|
153
|
+
* the ~50 `this.db.X(...)` callsites in this file don't all need
|
|
154
|
+
* touching at once; all writes should migrate to `this.localStore.X(...)`. */
|
|
155
|
+
private store: Store,
|
|
150
156
|
private imapManager: ImapManager,
|
|
151
157
|
) {
|
|
152
|
-
this.localStore =
|
|
153
|
-
this.syncQueue = new SyncQueue(db, imapManager);
|
|
154
|
-
this.reconciler = new Reconciler(db, imapManager, this.syncQueue);
|
|
158
|
+
this.localStore = store;
|
|
159
|
+
this.syncQueue = new SyncQueue(store.db, imapManager);
|
|
160
|
+
this.reconciler = new Reconciler(store.db, imapManager, this.syncQueue);
|
|
155
161
|
this.reconciler.start();
|
|
156
162
|
|
|
157
163
|
// Invalidate caches when their source files change on disk / GDrive.
|
|
158
|
-
// configChanged fires for any *.jsonc the watcher tracks (accounts,
|
|
159
|
-
// preferences, allowlist, etc.); broadly invalidate settings too so
|
|
160
|
-
// a preferences edit takes effect on the next getMessage / refresh.
|
|
161
|
-
// LocalStore also caches allowlist + settings to keep .eml display
|
|
162
|
-
// off the GDrive hot path; nudge it on the same event.
|
|
163
164
|
this.imapManager.on?.("configChanged", (filename: string) => {
|
|
164
165
|
if (filename === "accounts.jsonc") this._accountsCache = null;
|
|
165
166
|
this._settingsCache = null;
|
|
@@ -169,15 +170,15 @@ export class MailxService implements MailxApi {
|
|
|
169
170
|
console.error(` [contacts] reload failed: ${e?.message || e}`));
|
|
170
171
|
}
|
|
171
172
|
});
|
|
172
|
-
// Wire DB → cloud flush. Debounced to absorb bursts
|
|
173
|
-
|
|
174
|
-
// long enough that the steady state is one cloud write per sync,
|
|
175
|
-
// short enough that quitting after a single send still flushes.
|
|
176
|
-
this.db.setOnContactsChanged(() => this.markContactsDirty());
|
|
177
|
-
|
|
173
|
+
// Wire DB → cloud flush. Debounced to absorb bursts.
|
|
174
|
+
this.store.db.setOnContactsChanged(() => this.markContactsDirty());
|
|
178
175
|
// Initial load of contacts.jsonc — fire-and-forget; missing file is fine.
|
|
179
176
|
this.loadContactsConfig().catch(() => { /* file may not exist yet */ });
|
|
180
177
|
}
|
|
178
|
+
/** Transitional getter — direct DB access from MailxService for the
|
|
179
|
+
* ~50 callsites that haven't yet migrated to Store methods. Future:
|
|
180
|
+
* every mutation routes through `this.localStore.X(...)`. */
|
|
181
|
+
private get db(): MailxDB { return this.store.db; }
|
|
181
182
|
|
|
182
183
|
private _contactsFlushTimer: ReturnType<typeof setTimeout> | null = null;
|
|
183
184
|
private _contactsFlushInFlight = false;
|
|
@@ -1,147 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* LocalStore —
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* network calls.
|
|
8
|
-
*
|
|
9
|
-
* UI-side IPC dispatch lands here. The reconciler / sync queue is a
|
|
10
|
-
* separate concern — it owns network IO and signals completed work via
|
|
11
|
-
* events that the UI listens for. The two never call each other directly.
|
|
12
|
-
*
|
|
13
|
-
* This file is part of the local-first refactor (docs/local-first-plan.md).
|
|
14
|
-
* Step 1 of that plan: introduce the facade, prove it compiles, route
|
|
15
|
-
* one IPC method through it as proof-of-pattern. Subsequent steps migrate
|
|
16
|
-
* remaining call sites and remove the server-fetch path from the UI.
|
|
2
|
+
* LocalStore — DEPRECATED. The Store moved to `@bobfrankston/mailx-store`
|
|
3
|
+
* (it's now the architectural nexus and lives in the package that owns
|
|
4
|
+
* the underlying DB + .eml file backend). Re-exported here only so any
|
|
5
|
+
* stale `./local-store.js` imports keep resolving while in-tree callers
|
|
6
|
+
* migrate to `import { Store } from "@bobfrankston/mailx-store"`.
|
|
17
7
|
*/
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
/** What the UI gets back from a body read. Mirrors the historical
|
|
21
|
-
* `getMessage` shape so call-site migration is mechanical. `cached: false`
|
|
22
|
-
* means the body isn't on disk yet — the UI shows a "downloading…"
|
|
23
|
-
* placeholder and listens for `bodyAvailable` to re-render. The reconciler
|
|
24
|
-
* is responsible for actually fetching and emitting the event. */
|
|
25
|
-
export interface LocalMessage extends MessageEnvelope {
|
|
26
|
-
bodyHtml: string;
|
|
27
|
-
bodyText: string;
|
|
28
|
-
hasRemoteContent: boolean;
|
|
29
|
-
remoteAllowed: boolean;
|
|
30
|
-
attachments: Array<{
|
|
31
|
-
id: number;
|
|
32
|
-
filename: string;
|
|
33
|
-
mimeType: string;
|
|
34
|
-
size: number;
|
|
35
|
-
contentId: string;
|
|
36
|
-
}>;
|
|
37
|
-
cached: boolean;
|
|
38
|
-
deliveredTo: string;
|
|
39
|
-
returnPath: string;
|
|
40
|
-
listUnsubscribe: string;
|
|
41
|
-
listUnsubscribeMail: string;
|
|
42
|
-
listUnsubscribeHttp: string;
|
|
43
|
-
listUnsubscribeOneClick: boolean;
|
|
44
|
-
emlPath: string;
|
|
45
|
-
isFlagged: boolean;
|
|
46
|
-
}
|
|
47
|
-
export declare class LocalStore {
|
|
48
|
-
private db;
|
|
49
|
-
private bodyStore;
|
|
50
|
-
/** Event bus for Store mutations. Defaults to the process-singleton
|
|
51
|
-
* so cross-package subscribers (bin/mailx.ts forwarder → WebView,
|
|
52
|
-
* in-process reconciler triggers) see all writes without explicit
|
|
53
|
-
* wiring. Tests can pass a fresh StoreBus for isolation. */
|
|
54
|
-
readonly bus: StoreBus;
|
|
55
|
-
private static readonly PARSED_LRU_CAPACITY;
|
|
56
|
-
private parsedLru;
|
|
57
|
-
private parsedLruGet;
|
|
58
|
-
private parsedLruPut;
|
|
59
|
-
private _allowlistCache;
|
|
60
|
-
private _settingsCache;
|
|
61
|
-
private getCachedAllowlist;
|
|
62
|
-
private getCachedSettings;
|
|
63
|
-
invalidateConfigCaches(): void;
|
|
64
|
-
constructor(db: MailxDB, bodyStore: FileMessageStore,
|
|
65
|
-
/** Event bus for Store mutations. Defaults to the process-singleton
|
|
66
|
-
* so cross-package subscribers (bin/mailx.ts forwarder → WebView,
|
|
67
|
-
* in-process reconciler triggers) see all writes without explicit
|
|
68
|
-
* wiring. Tests can pass a fresh StoreBus for isolation. */
|
|
69
|
-
bus?: StoreBus);
|
|
70
|
-
/** DB-shape account list (id/name/email/lastSync). The richer
|
|
71
|
-
* AccountConfig (with imap/smtp/etc.) lives in accounts.jsonc and is
|
|
72
|
-
* loaded by mailx-settings, not the DB — that path stays in
|
|
73
|
-
* MailxService until step 3 of the local-first plan. */
|
|
74
|
-
getAccounts(): {
|
|
75
|
-
id: string;
|
|
76
|
-
name: string;
|
|
77
|
-
email: string;
|
|
78
|
-
lastSync: number;
|
|
79
|
-
}[];
|
|
80
|
-
getFolders(accountId: string): any[];
|
|
81
|
-
/** Look up a folder by RFC 6154 specialUse tag (`trash`, `drafts`, `sent`,
|
|
82
|
-
* `junk`, etc.) for the given account. Falls back to a case-insensitive
|
|
83
|
-
* path match for legacy rows where specialUse never got tagged.
|
|
84
|
-
* Returns null when the account has no such folder configured. */
|
|
85
|
-
findSpecialFolder(accountId: string, specialUse: string): {
|
|
86
|
-
id: number;
|
|
87
|
-
path: string;
|
|
88
|
-
} | null;
|
|
89
|
-
/** Single envelope by (account, uid, folder). Null when the row isn't
|
|
90
|
-
* in the DB — caller decides whether to show "deleted" or queue a
|
|
91
|
-
* server lookup via the reconciler. */
|
|
92
|
-
getMessageEnvelope(accountId: string, uid: number, folderId?: number): MessageEnvelope | null;
|
|
93
|
-
/** Paginated message list for a (account, folder, ...) query. */
|
|
94
|
-
getMessages(query: MessageQuery): PagedResult<MessageEnvelope>;
|
|
95
|
-
/** All-Inboxes view: union of every account's INBOX, paginated. */
|
|
96
|
-
getUnifiedInbox(page?: number, pageSize?: number): PagedResult<MessageEnvelope>;
|
|
97
|
-
/** Local FTS5 search. Server-scope search is the reconciler's job. */
|
|
98
|
-
searchMessages(query: string, page?: number, pageSize?: number, accountId?: string, folderId?: number, includeTrashSpam?: boolean): PagedResult<MessageEnvelope>;
|
|
99
|
-
/** Read a fully-parsed message (envelope + body + attachments) entirely
|
|
100
|
-
* from local state. Returns null when the envelope isn't known.
|
|
101
|
-
* Returns `{ ...envelope, cached: false }` when the envelope is known
|
|
102
|
-
* but the body file isn't on disk — UI shows a placeholder and the
|
|
103
|
-
* reconciler queues the fetch.
|
|
104
|
-
*
|
|
105
|
-
* `allowRemote=true` skips HTML sanitization. Used when the user has
|
|
106
|
-
* explicitly allowed remote content for this sender / domain. */
|
|
107
|
-
getMessage(accountId: string, uid: number, allowRemote: boolean, folderId?: number): Promise<LocalMessage | null>;
|
|
108
|
-
getCalendarEvents(accountId: string, fromMs: number, toMs: number): any[];
|
|
109
|
-
getTasks(accountId: string, includeCompleted?: boolean): any[];
|
|
110
|
-
searchContacts(query: string, limit?: number): any[];
|
|
111
|
-
listContacts(query: string, page?: number, pageSize?: number): any;
|
|
112
|
-
/** Update a message's flag set. Local DB write completes synchronously;
|
|
113
|
-
* the server-mirror enqueue is the caller's responsibility (typically
|
|
114
|
-
* via SyncQueue.enqueueFlag) so callers that don't want a server push
|
|
115
|
-
* — pure-local UI state like "pin in pane" — can skip it.
|
|
116
|
-
*
|
|
117
|
-
* Publishes:
|
|
118
|
-
* `message:<uuid>` { kind: "flagsChanged" }
|
|
119
|
-
* `folder:<id>` (auto fan-out)
|
|
120
|
-
*/
|
|
121
|
-
updateFlags(accountId: string, uid: number, folderId: number, flags: string[]): void;
|
|
122
|
-
/** Move a message between folders in the same account. Adds a tombstone
|
|
123
|
-
* on the Message-ID so the next sync doesn't re-import the pre-move row
|
|
124
|
-
* in the source folder before the server-side MOVE completes; tombstone
|
|
125
|
-
* is cleared on terminal IMAP failure (see processSyncActions).
|
|
126
|
-
*
|
|
127
|
-
* Returns true if a local row existed and was moved, false otherwise.
|
|
128
|
-
*
|
|
129
|
-
* Publishes:
|
|
130
|
-
* `message:<uuid>` { kind: "messageMoved", folderId: source, targetFolderId }
|
|
131
|
-
* `folder:<source>` and `folder:<target>` (auto fan-out + explicit count)
|
|
132
|
-
*/
|
|
133
|
-
moveMessage(accountId: string, uid: number, fromFolderId: number, targetFolderId: number): boolean;
|
|
134
|
-
/** Trash a message. If a trash folder is configured and the message is
|
|
135
|
-
* not already in it, this is a move-to-trash. If the message is already
|
|
136
|
-
* in trash (or no trash exists), it's a hard delete + body unlink.
|
|
137
|
-
*
|
|
138
|
-
* Returns "moved-to-trash" or "expunged" so the caller knows whether
|
|
139
|
-
* to enqueue an IMAP MOVE or a DELETE+EXPUNGE on the queue.
|
|
140
|
-
*/
|
|
141
|
-
trashMessage(accountId: string, uid: number, folderId: number, trashFolderId: number | null): "moved-to-trash" | "expunged";
|
|
142
|
-
/** Restore a message from trash back to its original folder. Local-only;
|
|
143
|
-
* caller handles the queue (cancel-pending-MOVE vs queue-counter-MOVE).
|
|
144
|
-
* Returns true if a local row was moved. */
|
|
145
|
-
undeleteMessage(accountId: string, uid: number, trashFolderId: number, originalFolderId: number): boolean;
|
|
146
|
-
}
|
|
8
|
+
export { Store as LocalStore } from "@bobfrankston/mailx-store";
|
|
9
|
+
export type { StoreMessage as LocalMessage } from "@bobfrankston/mailx-store";
|
|
147
10
|
//# sourceMappingURL=local-store.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local-store.d.ts","sourceRoot":"","sources":["local-store.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"local-store.d.ts","sourceRoot":"","sources":["local-store.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,KAAK,IAAI,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAChE,YAAY,EAAE,YAAY,IAAI,YAAY,EAAE,MAAM,2BAA2B,CAAC"}
|