@bobfrankston/rmfmail 1.0.546 → 1.0.549
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/mailx.js +13 -0
- package/bin/mailx.js.map +1 -1
- package/bin/mailx.ts +13 -0
- package/client/app.js +54 -1
- package/client/app.js.map +1 -1
- package/client/app.ts +53 -1
- package/client/components/message-list.js +1 -3
- package/client/components/message-list.js.map +1 -1
- package/client/components/message-list.ts +1 -3
- package/client/components/message-viewer.js +65 -97
- package/client/components/message-viewer.js.map +1 -1
- package/client/components/message-viewer.ts +51 -90
- package/client/compose/compose.js +2 -2
- package/client/compose/compose.js.map +1 -1
- package/client/compose/compose.ts +2 -2
- package/client/lib/api-client.js +5 -0
- package/client/lib/api-client.js.map +1 -1
- package/client/lib/api-client.ts +5 -1
- package/client/styles/components.css +0 -12
- package/package.json +1 -1
- package/packages/mailx-imap/index.d.ts +9 -1
- package/packages/mailx-imap/index.d.ts.map +1 -1
- package/packages/mailx-imap/index.js +25 -38
- package/packages/mailx-imap/index.js.map +1 -1
- package/packages/mailx-imap/index.ts +30 -42
- 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 +13 -0
- package/packages/mailx-server/index.js.map +1 -1
- package/packages/mailx-server/index.ts +14 -0
- package/packages/mailx-service/index.d.ts +26 -0
- package/packages/mailx-service/index.d.ts.map +1 -1
- package/packages/mailx-service/index.js +82 -235
- package/packages/mailx-service/index.js.map +1 -1
- package/packages/mailx-service/index.ts +45 -54
- package/packages/mailx-service/local-store.d.ts +5 -0
- package/packages/mailx-service/local-store.d.ts.map +1 -1
- package/packages/mailx-service/local-store.js +116 -12
- package/packages/mailx-service/local-store.js.map +1 -1
- package/packages/mailx-service/local-store.ts +5 -1
- package/packages/mailx-service/reconciler.d.ts +90 -0
- package/packages/mailx-service/reconciler.d.ts.map +1 -0
- package/packages/mailx-service/reconciler.js +209 -0
- package/packages/mailx-service/reconciler.js.map +1 -0
- package/packages/mailx-service/reconciler.ts +230 -0
- package/packages/mailx-service/sync-queue.d.ts +79 -0
- package/packages/mailx-service/sync-queue.d.ts.map +1 -0
- package/packages/mailx-service/sync-queue.js +118 -0
- package/packages/mailx-service/sync-queue.js.map +1 -0
- package/packages/mailx-service/sync-queue.ts +134 -0
- package/packages/mailx-settings/docs/contacts.md +6 -1
- package/packages/mailx-settings/docs/search.md +99 -0
- package/packages/mailx-settings/package.json +1 -1
- package/packages/mailx-store/file-store.d.ts +6 -0
- package/packages/mailx-store/file-store.d.ts.map +1 -1
- package/packages/mailx-store/file-store.js +8 -0
- package/packages/mailx-store/file-store.js.map +1 -1
- package/packages/mailx-store/file-store.ts +9 -0
- package/packages/mailx-store/package.json +1 -1
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SyncQueue — the formal enqueue surface for server-side mirroring of
|
|
3
|
+
* local actions.
|
|
4
|
+
*
|
|
5
|
+
* Local-first contract: every UI write commits to the local store first,
|
|
6
|
+
* then hands off here. Nothing in this file does network I/O; the
|
|
7
|
+
* Reconciler drains the queue.
|
|
8
|
+
*
|
|
9
|
+
* Implementation note: the underlying persistence already exists. Message
|
|
10
|
+
* ops live in `sync_actions` (drained by ImapManager.processSyncActions),
|
|
11
|
+
* non-message ops in `store_sync` (drained by the per-domain workers).
|
|
12
|
+
* This class is the public API surface — call sites switch to it so the
|
|
13
|
+
* architecture's seam is visible, the rules are enforceable, and we can
|
|
14
|
+
* later bolt on body-fetch / draft-push / send-push lanes without
|
|
15
|
+
* touching every call site again.
|
|
16
|
+
*
|
|
17
|
+
* Priority lanes (interactive > sync > prefetch > backfill) are tracked
|
|
18
|
+
* here as ordering hints; the actual scheduler is in the Reconciler.
|
|
19
|
+
*
|
|
20
|
+
* Part of docs/local-first-plan.md (steps 3-4).
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import type { MailxDB } from "@bobfrankston/mailx-store";
|
|
24
|
+
import type { ImapManager } from "@bobfrankston/mailx-imap";
|
|
25
|
+
|
|
26
|
+
export type Lane = "interactive" | "sync" | "prefetch" | "backfill";
|
|
27
|
+
|
|
28
|
+
/** In-memory body-fetch dedupe + ordering. We keep this as a Set rather
|
|
29
|
+
* than persisted rows because body fetches are idempotent (the file is
|
|
30
|
+
* named after the (folder, uid) pair) and a re-fetch on next click is
|
|
31
|
+
* cheap if a process crash drops the queued item. */
|
|
32
|
+
interface BodyFetchKey { accountId: string; folderId: number; uid: number; lane: Lane; attempts: number }
|
|
33
|
+
|
|
34
|
+
export class SyncQueue {
|
|
35
|
+
private bodyFetches = new Map<string, BodyFetchKey>();
|
|
36
|
+
|
|
37
|
+
constructor(
|
|
38
|
+
private db: MailxDB,
|
|
39
|
+
private imapManager: ImapManager,
|
|
40
|
+
) {}
|
|
41
|
+
|
|
42
|
+
// ── Message-mirror enqueues (commit local DB row first; then call here) ──
|
|
43
|
+
|
|
44
|
+
/** Queue a server-side move. The local rows have already been moved in
|
|
45
|
+
* the DB; the mirror reaches the server on the next reconciler pass. */
|
|
46
|
+
enqueueMove(accountId: string, uid: number, fromFolderId: number, toFolderId: number): void {
|
|
47
|
+
this.db.queueSyncAction(accountId, "move", uid, fromFolderId, { targetFolderId: toFolderId });
|
|
48
|
+
this.imapManager.processSyncActions(accountId).catch(() => { /* retried by reconciler */ });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Queue a server-side flag update (\Seen, \Flagged, \Answered, etc.). */
|
|
52
|
+
enqueueFlag(accountId: string, uid: number, folderId: number, flags: string[]): void {
|
|
53
|
+
this.db.queueSyncAction(accountId, "flags", uid, folderId, { flags });
|
|
54
|
+
this.imapManager.processSyncActions(accountId).catch(() => { /* retried */ });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Queue a server-side delete (or trash, depending on action). */
|
|
58
|
+
enqueueDelete(accountId: string, uid: number, folderId: number, kind: "delete" | "trash" = "delete"): void {
|
|
59
|
+
this.db.queueSyncAction(accountId, kind, uid, folderId, {});
|
|
60
|
+
this.imapManager.processSyncActions(accountId).catch(() => { /* retried */ });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Queue an IMAP APPEND of a draft body. Drafts already have crash
|
|
64
|
+
* recovery via the editing/.eml on disk, so this lane is in-memory
|
|
65
|
+
* fire-and-forget for now — the architectural value is centralizing
|
|
66
|
+
* the call site, not adding a second on-disk store for the same
|
|
67
|
+
* bytes. The reconciler retries on transient IMAP failure via the
|
|
68
|
+
* existing sync_actions backoff after first failure. */
|
|
69
|
+
enqueueDraftPush(accountId: string, rawMessage: string, previousDraftUid?: number, draftId?: string): void {
|
|
70
|
+
this.imapManager.saveDraft(accountId, rawMessage, previousDraftUid, draftId).catch((e: any) => {
|
|
71
|
+
console.error(` [sync-queue] draft push deferred (${draftId}): ${e?.message || e}`);
|
|
72
|
+
this.imapManager.emit("draftSaveDeferred", { accountId, draftId, error: String(e?.message || e) });
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Queue an outbox send. Today the outbox/<acct>/*.ltr directory IS
|
|
77
|
+
* the queue and a separate worker drains it; this seam is reserved
|
|
78
|
+
* for when that worker moves under the reconciler. */
|
|
79
|
+
enqueueSend(_accountId: string, _outboxId: string): void {
|
|
80
|
+
// No-op — outbox dir-based queue stays as-is per plan decision #5.
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ── Body fetch lane (in-memory; idempotent) ──
|
|
84
|
+
|
|
85
|
+
/** Request a body fetch. Dedupes per (account, folder, uid). The
|
|
86
|
+
* `lane` argument hints urgency: `interactive` is a click; `prefetch`
|
|
87
|
+
* is the background backfiller; `backfill` is the one-time post-sync
|
|
88
|
+
* catch-up. Reconciler picks higher priority first. */
|
|
89
|
+
enqueueBodyFetch(accountId: string, folderId: number, uid: number, lane: Lane = "interactive"): void {
|
|
90
|
+
const key = `${accountId}:${folderId}:${uid}`;
|
|
91
|
+
const existing = this.bodyFetches.get(key);
|
|
92
|
+
if (existing) {
|
|
93
|
+
// Upgrade lane if the new caller is more urgent.
|
|
94
|
+
const rank: Record<Lane, number> = { interactive: 0, sync: 1, prefetch: 2, backfill: 3 };
|
|
95
|
+
if (rank[lane] < rank[existing.lane]) existing.lane = lane;
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
this.bodyFetches.set(key, { accountId, folderId, uid, lane, attempts: 0 });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Re-enqueue a transient failure — the reconciler calls this after a
|
|
102
|
+
* fetch errored with a transient code. Returns `true` if the item was
|
|
103
|
+
* put back in the queue (still under retry budget) or `false` if its
|
|
104
|
+
* budget is exhausted (caller surfaces the error to the UI). */
|
|
105
|
+
requeueBodyFetch(key: BodyFetchKey, maxAttempts = 3): boolean {
|
|
106
|
+
if (key.attempts + 1 >= maxAttempts) return false;
|
|
107
|
+
const k = `${key.accountId}:${key.folderId}:${key.uid}`;
|
|
108
|
+
// Don't downgrade lane on retry; keep the one the original caller set.
|
|
109
|
+
this.bodyFetches.set(k, { ...key, attempts: key.attempts + 1 });
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Pop the next body-fetch task at or above `maxLane`. Used by the
|
|
114
|
+
* reconciler to drain interactive clicks before prefetch backfill. */
|
|
115
|
+
nextBodyFetch(maxLane: Lane = "backfill"): BodyFetchKey | null {
|
|
116
|
+
const rank: Record<Lane, number> = { interactive: 0, sync: 1, prefetch: 2, backfill: 3 };
|
|
117
|
+
const cap = rank[maxLane];
|
|
118
|
+
let best: BodyFetchKey | null = null;
|
|
119
|
+
for (const v of this.bodyFetches.values()) {
|
|
120
|
+
if (rank[v.lane] > cap) continue;
|
|
121
|
+
if (!best || rank[v.lane] < rank[best.lane]) best = v;
|
|
122
|
+
}
|
|
123
|
+
if (best) this.bodyFetches.delete(`${best.accountId}:${best.folderId}:${best.uid}`);
|
|
124
|
+
return best;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Pending count for diagnostics + the sync-status pill. */
|
|
128
|
+
pendingCount(): { messageActions: number; bodyFetches: number } {
|
|
129
|
+
return {
|
|
130
|
+
messageActions: this.db.getTotalPendingSyncCount(),
|
|
131
|
+
bodyFetches: this.bodyFetches.size,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -18,7 +18,11 @@
|
|
|
18
18
|
],
|
|
19
19
|
"discovered": [
|
|
20
20
|
{ "name": "Bob", "email": "bob@example.com", "useCount": 715, "lastUsed": 1777921829000 }
|
|
21
|
-
]
|
|
21
|
+
],
|
|
22
|
+
"groups": {
|
|
23
|
+
"Joes": ["joe@example.com", "jo@example.com"],
|
|
24
|
+
"Family": ["alice@example.com", "Joes"]
|
|
25
|
+
}
|
|
22
26
|
}
|
|
23
27
|
```
|
|
24
28
|
|
|
@@ -27,6 +31,7 @@
|
|
|
27
31
|
- **preferred** — manually-curated, top-ranked in autocomplete. Add people you actually want to reach quickly.
|
|
28
32
|
- **denylist** — addresses (lowercased) excluded from autocomplete entirely.
|
|
29
33
|
- **discovered** — auto-collected from sent/received mail. `useCount` × recency drives autocomplete ranking. Junk patterns (see below) are dropped at insertion.
|
|
34
|
+
- **groups** — mailing lists. Type the group name in To/Cc/Bcc and compose expands it into the member addresses on send. Each value is an array of either email addresses (`"a@b.com"` or `"Name <a@b.com>"`) or other group names (recursive aliasing — cycles are detected, depth capped at 10). Group lookup is case-insensitive on the key. The two `Joes` example above plus the `Family` example show flat membership and one group nested inside another.
|
|
30
35
|
|
|
31
36
|
## Global junk filter
|
|
32
37
|
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Search
|
|
2
|
+
|
|
3
|
+
> **Owned by rmfmail. Do not edit this file** — it's reference documentation.
|
|
4
|
+
|
|
5
|
+
Search in rmfmail runs in one of three modes depending on what you've selected:
|
|
6
|
+
|
|
7
|
+
- **All folders** (default) — searches your **local cache** using SQLite FTS5
|
|
8
|
+
- **This folder** — instant client-side filter on visible rows; full FTS5 search on Enter
|
|
9
|
+
- **Server** (the "Server" checkbox) — sends the query to the mail server (IMAP SEARCH on bobma-style accounts; **not yet wired to Gmail API for Gmail accounts** — see "Limitations" below)
|
|
10
|
+
|
|
11
|
+
The mode you're in dictates which query operators work. Scope matters.
|
|
12
|
+
|
|
13
|
+
## Common shortcuts (work in every mode)
|
|
14
|
+
|
|
15
|
+
These are parsed by mailx before dispatch, so they apply to all three scopes:
|
|
16
|
+
|
|
17
|
+
| Form | Effect |
|
|
18
|
+
|---|---|
|
|
19
|
+
| `from:bob` | Match sender substring |
|
|
20
|
+
| `to:eleanor` | Match recipient substring |
|
|
21
|
+
| `subject:lunch` | Match subject substring (everything until the next `keyword:` or end) |
|
|
22
|
+
| `/regex/` | Client-side regex over the currently-visible rows. Local only — never sent to the server. |
|
|
23
|
+
|
|
24
|
+
The remaining (unqualified) text becomes the **body** search term in the server query, or a free-text FTS5 phrase in local mode.
|
|
25
|
+
|
|
26
|
+
## Mode 1: Local search (FTS5) — default
|
|
27
|
+
|
|
28
|
+
Uses SQLite's FTS5 full-text index built from envelopes + locally-cached bodies. Fast, indexed, ~1ms per query.
|
|
29
|
+
|
|
30
|
+
Supported FTS5 syntax (when the search box has more than one word and no scope checkbox):
|
|
31
|
+
|
|
32
|
+
| Operator | Example | Meaning |
|
|
33
|
+
|---|---|---|
|
|
34
|
+
| `term1 term2` | `bob lunch` | both must appear (implicit AND) |
|
|
35
|
+
| `term1 OR term2` | `bob OR eleanor` | either |
|
|
36
|
+
| `term1 NOT term2` | `bob NOT spam` | first without the second |
|
|
37
|
+
| `"phrase"` | `"happy birthday"` | exact phrase |
|
|
38
|
+
| `term*` | `lunch*` | prefix |
|
|
39
|
+
| `^col:term` | `^subject:budget` | restrict to one indexed column (subject, body, from, to) |
|
|
40
|
+
| `NEAR(a b, 5)` | `NEAR(bob lunch, 5)` | both within 5 tokens of each other |
|
|
41
|
+
|
|
42
|
+
**Case**: FTS5 is **case-insensitive**. `AND`/`OR`/`NOT` work in any case (`and`, `OR`, `Not` are equivalent).
|
|
43
|
+
|
|
44
|
+
What's actually indexed: subject, from, to, cc, snippet (first ~200 chars of body), and the full body text *if* the body has been downloaded locally (the blue dot in the list). Bodies that haven't been prefetched aren't searchable until they are.
|
|
45
|
+
|
|
46
|
+
## Mode 2: This-folder filter
|
|
47
|
+
|
|
48
|
+
When the scope dropdown is set to "This folder" and you type, mailx **client-side filters the currently rendered rows** instantly — no server hit, no FTS5. Just substring match across each visible row's text.
|
|
49
|
+
|
|
50
|
+
Press **Enter** to escalate to a full FTS5 search of the folder.
|
|
51
|
+
|
|
52
|
+
`/regex/` works here for fast on-screen filtering.
|
|
53
|
+
|
|
54
|
+
## Mode 3: Server search
|
|
55
|
+
|
|
56
|
+
Tick the "Server" checkbox and the query goes to the mail server.
|
|
57
|
+
|
|
58
|
+
### bobma / Dovecot (IMAP SEARCH)
|
|
59
|
+
|
|
60
|
+
Standard IMAP keys, server-side. Combinable with implicit AND. mailx parses your `from:`, `to:`, `subject:` qualifiers and forwards them as IMAP keys; remaining text becomes a `BODY` search.
|
|
61
|
+
|
|
62
|
+
| Key | Example | Meaning |
|
|
63
|
+
|---|---|---|
|
|
64
|
+
| `BODY <text>` | (default for unqualified words) | Substring of body |
|
|
65
|
+
| `TEXT <text>` | (not exposed in the UI yet) | Substring of header + body |
|
|
66
|
+
| `SUBJECT <text>` | `subject:budget` | Substring of subject |
|
|
67
|
+
| `FROM <addr>` | `from:bob` | Substring of From line |
|
|
68
|
+
| `TO <addr>` | `to:eleanor` | Substring of To line |
|
|
69
|
+
| `SINCE <date>` | (not exposed) | RFC 822 date |
|
|
70
|
+
|
|
71
|
+
**Case**: IMAP SEARCH is **case-insensitive** (per RFC 3501 §6.4.4). `bob AND eleanor` and `bob and eleanor` behave the same on Dovecot.
|
|
72
|
+
|
|
73
|
+
**`AND` / `OR`**: IMAP's grammar is implicit-AND between keys. There's no literal `AND` keyword — typing `bob AND eleanor` would be sent as a body search for the literal three-word string. To OR keys you'd use IMAP's `OR <key1> <key2>` prefix syntax, which mailx doesn't expose in the UI today.
|
|
74
|
+
|
|
75
|
+
So if you typed `bob AND eleanor` on bobma and got results, what likely happened is the body literally contained the string "bob and eleanor" (case-insensitive). Apparent "AND" success is coincidence with the literal text.
|
|
76
|
+
|
|
77
|
+
### Gmail (server-search currently unwired)
|
|
78
|
+
|
|
79
|
+
`searchAndFetchOnServer` is IMAP-only and `withConnection` doesn't exist for Gmail-API accounts. Server-scope queries on Gmail today **return nothing new** — they only hit local cache. This is a known gap; the fix is to add a Gmail-API path that uses Google's `q=` query parameter (full Gmail search syntax: `from:`, `subject:`, `has:attachment`, `older_than:1y`, `label:`, etc., with proper boolean `AND`/`OR`).
|
|
80
|
+
|
|
81
|
+
That explains the asymmetry you may have noticed: `bob AND eleanor` "worked" against bobma (literal-text coincidence) but not against Gmail (no IMAP/API call ever fired).
|
|
82
|
+
|
|
83
|
+
## Limitations
|
|
84
|
+
|
|
85
|
+
- **No regex on the server side** (any provider). `/pattern/` only filters the visible local rows.
|
|
86
|
+
- **Server search results are stored locally** (envelope-only) on first hit. So a server search is also a backfill of envelopes for those messages — they appear next to local results without re-fetching.
|
|
87
|
+
- **Body search on bodies you haven't prefetched** only works server-side. The local FTS5 index can't search what isn't downloaded.
|
|
88
|
+
|
|
89
|
+
## Practical patterns
|
|
90
|
+
|
|
91
|
+
| Goal | What to type | Scope |
|
|
92
|
+
|---|---|---|
|
|
93
|
+
| Quick keyword in current folder | `lunch` | This folder (instant) |
|
|
94
|
+
| All-time mail from someone | `from:bob.frankston@example.com` | All folders (FTS5) |
|
|
95
|
+
| Mail mentioning two people | `bob eleanor` | All folders (FTS5; implicit AND) |
|
|
96
|
+
| Either of two terms | `bob OR eleanor` | All folders (FTS5) |
|
|
97
|
+
| Exact phrase | `"open source"` | All folders (FTS5) |
|
|
98
|
+
| Regex over what's on screen | `/^Re: \[mailx\]/` | Any (client-side) |
|
|
99
|
+
| Old mail beyond `historyDays` | type query, then check the **Server** checkbox | Server (Dovecot only today) |
|
|
@@ -28,6 +28,12 @@ export declare class FileMessageStore implements MessageStore {
|
|
|
28
28
|
* it in `body_path`. The (folderId, uid) args are kept for interface
|
|
29
29
|
* compatibility; they do NOT affect the filename. */
|
|
30
30
|
putMessage(accountId: string, _folderId: number, _uid: number, raw: Buffer): Promise<string>;
|
|
31
|
+
/** Resolve a stored body_path (relative or absolute) to an absolute
|
|
32
|
+
* filesystem path. Returns "" if the input doesn't resolve to a file
|
|
33
|
+
* inside the store. UI / "Source" actions need the absolute path so
|
|
34
|
+
* they can open / pass it to OS file pickers without re-introducing
|
|
35
|
+
* the basePath context every time. */
|
|
36
|
+
absolutePath(stored: string): string;
|
|
31
37
|
/** Read by stored path (relative or absolute). */
|
|
32
38
|
readByPath(stored: string): Promise<Buffer>;
|
|
33
39
|
hasByPath(stored: string): Promise<boolean>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-store.d.ts","sourceRoot":"","sources":["file-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAE9D,qBAAa,gBAAiB,YAAW,YAAY;IACrC,OAAO,CAAC,QAAQ;gBAAR,QAAQ,EAAE,MAAM;IAIpC;;;sCAGkC;IAClC,OAAO,CAAC,eAAe;IAMvB;;;gDAG4C;IAC5C,OAAO,CAAC,aAAa;IAQrB;;0DAEsD;IAChD,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAQlG,kDAAkD;IAC5C,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAM3C,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAK3C,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMjD;;;;;;;;;uDASmD;IACnD,yBAAyB,CAAC,IAAI,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,EAC7C,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM;IA6BjF,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM;IAGrE,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAGhF,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAGjF,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAG1F"}
|
|
1
|
+
{"version":3,"file":"file-store.d.ts","sourceRoot":"","sources":["file-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAE9D,qBAAa,gBAAiB,YAAW,YAAY;IACrC,OAAO,CAAC,QAAQ;gBAAR,QAAQ,EAAE,MAAM;IAIpC;;;sCAGkC;IAClC,OAAO,CAAC,eAAe;IAMvB;;;gDAG4C;IAC5C,OAAO,CAAC,aAAa;IAQrB;;0DAEsD;IAChD,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAQlG;;;;2CAIuC;IACvC,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IAIpC,kDAAkD;IAC5C,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAM3C,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAK3C,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMjD;;;;;;;;;uDASmD;IACnD,yBAAyB,CAAC,IAAI,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,EAC7C,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM;IA6BjF,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM;IAGrE,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAGhF,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAGjF,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAG1F"}
|
|
@@ -51,6 +51,14 @@ export class FileMessageStore {
|
|
|
51
51
|
fs.writeFileSync(abs, raw);
|
|
52
52
|
return rel;
|
|
53
53
|
}
|
|
54
|
+
/** Resolve a stored body_path (relative or absolute) to an absolute
|
|
55
|
+
* filesystem path. Returns "" if the input doesn't resolve to a file
|
|
56
|
+
* inside the store. UI / "Source" actions need the absolute path so
|
|
57
|
+
* they can open / pass it to OS file pickers without re-introducing
|
|
58
|
+
* the basePath context every time. */
|
|
59
|
+
absolutePath(stored) {
|
|
60
|
+
return this.resolveStored(stored);
|
|
61
|
+
}
|
|
54
62
|
/** Read by stored path (relative or absolute). */
|
|
55
63
|
async readByPath(stored) {
|
|
56
64
|
const abs = this.resolveStored(stored);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-store.js","sourceRoot":"","sources":["file-store.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"file-store.js","sourceRoot":"","sources":["file-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -54,6 +54,15 @@ export class FileMessageStore implements MessageStore {
|
|
|
54
54
|
return rel;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
/** Resolve a stored body_path (relative or absolute) to an absolute
|
|
58
|
+
* filesystem path. Returns "" if the input doesn't resolve to a file
|
|
59
|
+
* inside the store. UI / "Source" actions need the absolute path so
|
|
60
|
+
* they can open / pass it to OS file pickers without re-introducing
|
|
61
|
+
* the basePath context every time. */
|
|
62
|
+
absolutePath(stored: string): string {
|
|
63
|
+
return this.resolveStored(stored);
|
|
64
|
+
}
|
|
65
|
+
|
|
57
66
|
/** Read by stored path (relative or absolute). */
|
|
58
67
|
async readByPath(stored: string): Promise<Buffer> {
|
|
59
68
|
const abs = this.resolveStored(stored);
|