@bobfrankston/mailx-settings 0.1.28 → 0.1.30
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/docs/instance-model-plan.md +75 -0
- package/index.d.ts +21 -0
- package/index.d.ts.map +1 -1
- package/index.js +19 -0
- package/package.json +1 -1
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Instance model — design plan (2026-06-16)
|
|
2
|
+
|
|
3
|
+
## Decision (Bob)
|
|
4
|
+
Model **B**: one record **per instance**, not per Message-ID. A copy of a message in
|
|
5
|
+
two folders is **two independent instances** with independent state (read/flag/star,
|
|
6
|
+
body, and — critically — **editable content**). Message-IDs are therefore **NOT globally
|
|
7
|
+
unique**; every code path that assumed "Message-ID → one row" must be made deliberate.
|
|
8
|
+
Bob's emphasis: **database integrity**.
|
|
9
|
+
|
|
10
|
+
## Identity
|
|
11
|
+
- **Instance = one `messages` row.** Natural key `(account_id, folder_id, uid)` (already
|
|
12
|
+
the schema's UNIQUE). Each row keeps its own stable `uuid`, `flags_json`, `body_path`,
|
|
13
|
+
and content (subject/body/etc. — may diverge once edited).
|
|
14
|
+
- `message_id` becomes a **non-unique indexed attribute**, used for threading and for
|
|
15
|
+
*display* dedup only — never as an identity key.
|
|
16
|
+
- **Drop the move-detect Message-ID collapse.** A new `(folder, uid)` is ALWAYS its own
|
|
17
|
+
instance, even when the same `message_id` exists in another folder.
|
|
18
|
+
|
|
19
|
+
## message_folders
|
|
20
|
+
Redundant under B (instance already = a `messages` row with folder_id/uid). It becomes
|
|
21
|
+
1:1 with `messages`. Plan: **retire it** — revert reads to `messages.folder_id`/`uid`,
|
|
22
|
+
removing the dual-representation integrity hazard. Staged so reads never break mid-flight.
|
|
23
|
+
|
|
24
|
+
## Server truth & move vs copy (reconcile)
|
|
25
|
+
- An instance exists iff the server lists `(folder, uid)`. Per-folder reconcile drops
|
|
26
|
+
instances the server no longer confirms. This cleans accidental stale-move-sources
|
|
27
|
+
while **preserving real copies** (server confirms both) — no exclusivity.
|
|
28
|
+
- **Server-side MOVE** (uid vanished from A, appeared in B, same message_id, A had exactly
|
|
29
|
+
ONE such instance): transfer the old instance's local state (uuid, flags, body_path,
|
|
30
|
+
local edits) to the new instance, then drop the old. Ambiguous case (copies present) →
|
|
31
|
+
treat as a fresh instance + re-fetch. Edits survive unambiguous moves.
|
|
32
|
+
|
|
33
|
+
## Copy / Edit operations
|
|
34
|
+
- **Copy to folder** (user action or server-side sieve copy): a new instance. User copy
|
|
35
|
+
queues a server `COPY`; sieve copy simply appears as a second server-confirmed instance.
|
|
36
|
+
- **Edit instance**: mutates only that instance's row + its own body file. No other
|
|
37
|
+
instance is touched. (Whether edits push to the server is a separate question.)
|
|
38
|
+
|
|
39
|
+
## Display dedup
|
|
40
|
+
- Unified "All Inboxes" still collapses copies by `message_id` for display, but each
|
|
41
|
+
instance is independently selectable/openable/editable.
|
|
42
|
+
- An **edited** instance (content diverged from its siblings) is shown **separately** —
|
|
43
|
+
flag `edited=1` excludes it from the dedup group.
|
|
44
|
+
|
|
45
|
+
## No migration — rebuild
|
|
46
|
+
Per the standing rule (local store is a cache): **wipe local + re-sync** into the new
|
|
47
|
+
model rather than backfill 136k rows. Avoids fragile migration code. One full re-sync.
|
|
48
|
+
|
|
49
|
+
## Integrity safeguards (Bob's ask)
|
|
50
|
+
- `verifyInstances()` self-check on boot + a `-verify` command: every `messages` row has a
|
|
51
|
+
valid folder; no two rows share `(account, folder, uid)`; `uuid` globally unique; every
|
|
52
|
+
referenced `body_path` exists; `thread_id` resolvable. Log + count violations.
|
|
53
|
+
- All instance writes go through one chokepoint (`upsertInstance`) — no scattered INSERTs.
|
|
54
|
+
- Audit-log every state transfer on move and every reconcile drop (already partly done).
|
|
55
|
+
|
|
56
|
+
## Staged execution (each stage compiles, ships, is verifiable; no broken intermediates)
|
|
57
|
+
1. **Plan + integrity scaffold** (this doc) + `verifyInstances()` read-only checker (no
|
|
58
|
+
behavior change) — measure current violations as a baseline.
|
|
59
|
+
2. **Stop the collapse**: `upsertMessage` creates a new instance per `(folder,uid)`;
|
|
60
|
+
remove cross-folder rebind. Keep same-folder uid-update. Gate behind rebuild.
|
|
61
|
+
3. **Reconcile = server truth** per folder (drop unconfirmed; preserve copies); move-state
|
|
62
|
+
transfer for the unambiguous single-instance move.
|
|
63
|
+
4. **Retire `message_folders`**: point reads back at `messages.folder_id`/`uid`; drop the
|
|
64
|
+
join + its boot migration.
|
|
65
|
+
5. **Copy/Edit**: explicit copy-to-folder (new instance + server COPY) and per-instance
|
|
66
|
+
edit; `edited` flag + display-dedup exclusion.
|
|
67
|
+
6. **Rebuild + verify** on the live DB; confirm copies preserved, accidents gone, 0
|
|
68
|
+
integrity violations.
|
|
69
|
+
|
|
70
|
+
## Open sub-decisions (confirm before stage 2)
|
|
71
|
+
- **A.** Retire `message_folders` (recommended — kills the dual representation) vs keep it.
|
|
72
|
+
- **B.** Move-state transfer for unambiguous moves (recommended — preserves edits) vs
|
|
73
|
+
always-fresh-instance on move (simpler, loses local read-state/edits on a server move).
|
|
74
|
+
- **C.** Do instance **edits push to the server** (APPEND new + delete old), or stay
|
|
75
|
+
local-only annotations? (Affects whether an edit changes the server message_id.)
|
package/index.d.ts
CHANGED
|
@@ -49,6 +49,27 @@ export declare function getStorageInfo(): {
|
|
|
49
49
|
configDir?: string;
|
|
50
50
|
cloudError?: string;
|
|
51
51
|
};
|
|
52
|
+
export interface ProviderDefaults {
|
|
53
|
+
label: string;
|
|
54
|
+
imap: {
|
|
55
|
+
host: string;
|
|
56
|
+
port: number;
|
|
57
|
+
tls: boolean;
|
|
58
|
+
auth: "password" | "oauth2";
|
|
59
|
+
};
|
|
60
|
+
smtp: {
|
|
61
|
+
host: string;
|
|
62
|
+
port: number;
|
|
63
|
+
tls: boolean;
|
|
64
|
+
auth: "password" | "oauth2";
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/** Single source of truth for provider IMAP/SMTP defaults by email domain.
|
|
68
|
+
* desktop setup (`detectEmailProvider`) and ANY other setup path must use THIS,
|
|
69
|
+
* not a private re-listing — `detectEmailProvider` only knew Gmail+Outlook, so
|
|
70
|
+
* AOL/Yahoo/iCloud never auto-configured (Bob 2026-06-19, economy of mechanism /
|
|
71
|
+
* "prefer Drive with local caching should be a shared component"). */
|
|
72
|
+
export declare function providerForDomain(domain: string): ProviderDefaults | undefined;
|
|
52
73
|
/** Canonical form of an email address — applies provider-specific
|
|
53
74
|
* equivalences so two superficially-different strings that resolve to the
|
|
54
75
|
* SAME mailbox produce the same canonical key. Today: Gmail (and
|
package/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAC;AAyG5G,QAAA,MAAM,SAAS,QAA4E,CAAC;AAiE5F,qFAAqF;AACrF,KAAK,kBAAkB,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,CAAC,EAAE;IAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,KAAK,IAAI,CAAC;AAE/G,wBAAgB,YAAY,CAAC,EAAE,EAAE,kBAAkB,GAAG,MAAM,IAAI,CAM/D;AAOD,wBAAgB,iBAAiB,IAAI,MAAM,GAAG,IAAI,CAA2B;AAU7E,iBAAS,YAAY,IAAI,MAAM,CAgB9B;AAOD,sEAAsE;AACtE,wBAAsB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAgDxE;AAED;;;;8BAI8B;AAC9B,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAkB9E;AAED;;qCAEqC;AACrC,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAsCjF;AAyBD,2CAA2C;AAC3C,wBAAgB,WAAW,IAAI,OAAO,CAErC;AAED,4CAA4C;AAC5C,wBAAgB,cAAc,IAAI;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,GAAG,KAAK,GAAG,OAAO,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,CA+B3L;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"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"}
|
package/index.js
CHANGED
|
@@ -535,7 +535,26 @@ const PROVIDERS = {
|
|
|
535
535
|
imap: { host: "imap.mail.me.com", port: 993, tls: true, auth: "password" },
|
|
536
536
|
smtp: { host: "smtp.mail.me.com", port: 587, tls: true, auth: "password" },
|
|
537
537
|
},
|
|
538
|
+
"me.com": {
|
|
539
|
+
label: "iCloud",
|
|
540
|
+
imap: { host: "imap.mail.me.com", port: 993, tls: true, auth: "password" },
|
|
541
|
+
smtp: { host: "smtp.mail.me.com", port: 587, tls: true, auth: "password" },
|
|
542
|
+
},
|
|
543
|
+
"verizon.net": {
|
|
544
|
+
// Verizon mail is hosted by AOL/Yahoo (Verizon migrated to AOL's stack).
|
|
545
|
+
label: "Verizon",
|
|
546
|
+
imap: { host: "imap.aol.com", port: 993, tls: true, auth: "password" },
|
|
547
|
+
smtp: { host: "smtp.aol.com", port: 587, tls: true, auth: "password" },
|
|
548
|
+
},
|
|
538
549
|
};
|
|
550
|
+
/** Single source of truth for provider IMAP/SMTP defaults by email domain.
|
|
551
|
+
* desktop setup (`detectEmailProvider`) and ANY other setup path must use THIS,
|
|
552
|
+
* not a private re-listing — `detectEmailProvider` only knew Gmail+Outlook, so
|
|
553
|
+
* AOL/Yahoo/iCloud never auto-configured (Bob 2026-06-19, economy of mechanism /
|
|
554
|
+
* "prefer Drive with local caching should be a shared component"). */
|
|
555
|
+
export function providerForDomain(domain) {
|
|
556
|
+
return PROVIDERS[(domain || "").toLowerCase()];
|
|
557
|
+
}
|
|
539
558
|
/** Canonical form of an email address — applies provider-specific
|
|
540
559
|
* equivalences so two superficially-different strings that resolve to the
|
|
541
560
|
* SAME mailbox produce the same canonical key. Today: Gmail (and
|