@bobfrankston/rmfmail 1.0.708 → 1.1.3
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 +9 -1
- package/bin/mailx.js.map +1 -1
- package/bin/mailx.ts +10 -1
- package/client/android-bootstrap.bundle.js +1 -1
- package/client/android-bootstrap.bundle.js.map +2 -2
- package/client/app.bundle.js +58 -1
- package/client/app.bundle.js.map +2 -2
- package/client/compose/compose.bundle.js +41 -1
- package/client/compose/compose.bundle.js.map +2 -2
- package/client/lib/api-client.js +41 -0
- package/client/lib/api-client.js.map +1 -1
- package/client/lib/api-client.ts +29 -0
- package/client/lib/message-state.js +23 -0
- package/client/lib/message-state.js.map +1 -1
- package/client/lib/message-state.ts +21 -0
- package/package.json +4 -3
- package/packages/mailx-bus/index.d.ts +122 -0
- package/packages/mailx-bus/index.d.ts.map +1 -0
- package/packages/mailx-bus/index.js +247 -0
- package/packages/mailx-bus/index.js.map +1 -0
- package/packages/mailx-bus/index.ts +275 -0
- package/packages/mailx-bus/package.json +19 -0
- package/packages/mailx-bus/store-events.d.ts +79 -0
- package/packages/mailx-bus/store-events.d.ts.map +1 -0
- package/packages/mailx-bus/store-events.js +155 -0
- package/packages/mailx-bus/store-events.js.map +1 -0
- package/packages/mailx-bus/store-events.ts +165 -0
- package/packages/mailx-bus/tsconfig.json +9 -0
- package/packages/mailx-core/index.d.ts.map +1 -1
- package/packages/mailx-core/index.js +25 -3
- package/packages/mailx-core/index.js.map +1 -1
- package/packages/mailx-core/index.ts +23 -3
- package/packages/mailx-host/package.json +1 -1
- package/packages/mailx-imap/index.d.ts +0 -37
- package/packages/mailx-imap/index.d.ts.map +1 -1
- package/packages/mailx-imap/index.js +17 -172
- package/packages/mailx-imap/index.js.map +1 -1
- package/packages/mailx-imap/index.ts +17 -179
- package/packages/mailx-imap/package-lock.json +2 -2
- package/packages/mailx-imap/package.json +1 -1
- package/packages/mailx-service/db-worker-client.d.ts +32 -0
- package/packages/mailx-service/db-worker-client.d.ts.map +1 -0
- package/packages/mailx-service/db-worker-client.js +66 -0
- package/packages/mailx-service/db-worker-client.js.map +1 -0
- package/packages/mailx-service/db-worker-client.ts +75 -0
- package/packages/mailx-service/db-worker.d.ts +39 -0
- package/packages/mailx-service/db-worker.d.ts.map +1 -0
- package/packages/mailx-service/db-worker.js +104 -0
- package/packages/mailx-service/db-worker.js.map +1 -0
- package/packages/mailx-service/db-worker.ts +153 -0
- package/packages/mailx-service/index.d.ts +16 -0
- package/packages/mailx-service/index.d.ts.map +1 -1
- package/packages/mailx-service/index.js +89 -77
- package/packages/mailx-service/index.js.map +1 -1
- package/packages/mailx-service/index.ts +83 -75
- package/packages/mailx-service/local-store.d.ts +54 -2
- package/packages/mailx-service/local-store.d.ts.map +1 -1
- package/packages/mailx-service/local-store.js +147 -2
- package/packages/mailx-service/local-store.js.map +1 -1
- package/packages/mailx-service/local-store.ts +147 -3
- package/packages/mailx-service/package.json +1 -0
- package/packages/mailx-service/reconciler.d.ts.map +1 -1
- package/packages/mailx-service/reconciler.js +28 -8
- package/packages/mailx-service/reconciler.js.map +1 -1
- package/packages/mailx-service/reconciler.ts +28 -8
- package/packages/mailx-service/sync-queue.d.ts +16 -0
- package/packages/mailx-service/sync-queue.d.ts.map +1 -1
- package/packages/mailx-service/sync-queue.js +33 -3
- package/packages/mailx-service/sync-queue.js.map +1 -1
- package/packages/mailx-service/sync-queue.ts +33 -3
- package/packages/mailx-settings/package.json +1 -1
- package/packages/mailx-store/bus.d.ts +79 -0
- package/packages/mailx-store/bus.d.ts.map +1 -0
- package/packages/mailx-store/bus.js +155 -0
- package/packages/mailx-store/bus.js.map +1 -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 +5 -0
- package/packages/mailx-store/index.js.map +1 -1
- package/packages/mailx-store/index.ts +7 -0
- package/packages/mailx-store/package.json +2 -1
- package/packages/mailx-store-web/db.d.ts.map +1 -1
- package/packages/mailx-store-web/db.js +9 -1
- package/packages/mailx-store-web/db.js.map +1 -1
- package/packages/mailx-store-web/db.ts +9 -1
- package/packages/mailx-store-web/package.json +2 -1
- package/packages/mailx-store-web/sync-manager.d.ts +4 -0
- package/packages/mailx-store-web/sync-manager.d.ts.map +1 -1
- package/packages/mailx-store-web/sync-manager.js +32 -4
- package/packages/mailx-store-web/sync-manager.js.map +1 -1
- package/packages/mailx-store-web/sync-manager.ts +28 -4
- /package/packages/mailx-imap/{node_modules.npmglobalize-stash-24548 → node_modules.npmglobalize-stash-444}/.package-lock.json +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @bobfrankston/mailx-bus
|
|
3
|
+
*
|
|
4
|
+
* Typed message bus that connects mailx's factors. Each factor (DB, sync,
|
|
5
|
+
* UI, compose, calendar, …) is a peer that publishes/subscribes to events
|
|
6
|
+
* and serves/calls request-reply methods over the same bus.
|
|
7
|
+
*
|
|
8
|
+
* **Why this exists.** mailx historically had one monolithic service on one
|
|
9
|
+
* Node event loop: UI reads, IMAP sync, body parsing, OAuth refresh, prefetch,
|
|
10
|
+
* calendar polling — all on the same thread. A 14-minute synchronous freeze
|
|
11
|
+
* in any one path froze every other path. There was no separation between
|
|
12
|
+
* "answer a click" and "wait for Dovecot." The bus is the mechanism that
|
|
13
|
+
* makes those independent: a factor freezing is local; other factors keep
|
|
14
|
+
* answering.
|
|
15
|
+
*
|
|
16
|
+
* **Shape.** A bus exposes two primitives:
|
|
17
|
+
* - `publish(topic, payload)` / `subscribe(topic, handler)` — fire-and-
|
|
18
|
+
* forget events. Many subscribers per topic.
|
|
19
|
+
* - `request(method, args) → Promise<result>` / `register(method, handler)`
|
|
20
|
+
* — RPC. One handler per method. Caller awaits the reply.
|
|
21
|
+
*
|
|
22
|
+
* No method-name dispatch on a god object. Each factor declares the topics
|
|
23
|
+
* and methods it owns. Other factors hold a reference to the bus, not to
|
|
24
|
+
* each other.
|
|
25
|
+
*
|
|
26
|
+
* **Two transports, one interface.**
|
|
27
|
+
* - `InProcessBus`: in-memory Map of handlers. Used in tests, in Android
|
|
28
|
+
* (single-threaded JS), and for the same-thread half of a bridge.
|
|
29
|
+
* - `WorkerBus`: connects to a peer via `worker_threads.MessagePort` (Node)
|
|
30
|
+
* or `MessagePort` (browser). Uses `postMessage`, with structured
|
|
31
|
+
* clone, so the two sides can live in different threads.
|
|
32
|
+
*
|
|
33
|
+
* Both implement `Bus`. Callers write against `Bus`. Where the peer lives is
|
|
34
|
+
* a deploy decision, not an API decision.
|
|
35
|
+
*
|
|
36
|
+
* **Buffer/Uint8Array note (Node).** `worker_threads.postMessage` clones a
|
|
37
|
+
* Node `Buffer` as a plain `Uint8Array`. Callers passing binary data through
|
|
38
|
+
* the bus across a worker boundary must accept that the receiving side sees
|
|
39
|
+
* `Uint8Array` — they should re-wrap with `Buffer.from(u.buffer, u.byteOffset,
|
|
40
|
+
* u.byteLength)` before handing the value to APIs that test
|
|
41
|
+
* `Buffer.isBuffer(x)`. The bus does not do this transparently; it is a
|
|
42
|
+
* transport concern that varies by what the payload actually is.
|
|
43
|
+
*
|
|
44
|
+
* **Errors.** A handler that throws is converted to a rejection on the
|
|
45
|
+
* caller's `request()` promise. The error's `message` survives the
|
|
46
|
+
* structured-clone barrier; the stack trace does not (Node strips it).
|
|
47
|
+
*
|
|
48
|
+
* **Lifecycle.** Subscribers receive a `dispose()` function from `subscribe`.
|
|
49
|
+
* Registered methods receive one from `register`. Dispose unhooks. A bus
|
|
50
|
+
* itself has no global teardown — closing the underlying port (when using
|
|
51
|
+
* `WorkerBus`) is the caller's responsibility.
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
/** Method handler — receives the request payload, returns the response. May
|
|
55
|
+
* be async. A throw becomes a request() rejection on the caller. */
|
|
56
|
+
export type MethodHandler<A = any, R = any> = (args: A) => R | Promise<R>;
|
|
57
|
+
|
|
58
|
+
/** Topic handler — fire-and-forget; the return value is ignored. */
|
|
59
|
+
export type TopicHandler<P = any> = (payload: P) => void;
|
|
60
|
+
|
|
61
|
+
/** Unsubscribe / unregister token returned from `subscribe` / `register`. */
|
|
62
|
+
export type Dispose = () => void;
|
|
63
|
+
|
|
64
|
+
export interface Bus {
|
|
65
|
+
/** Fire an event. Returns synchronously; subscribers are invoked
|
|
66
|
+
* asynchronously (next microtask) so a publisher doesn't observe its
|
|
67
|
+
* own re-entry. */
|
|
68
|
+
publish<P = any>(topic: string, payload: P): void;
|
|
69
|
+
/** Listen to events on `topic`. Returns a dispose function. */
|
|
70
|
+
subscribe<P = any>(topic: string, handler: TopicHandler<P>): Dispose;
|
|
71
|
+
/** Call `method` on its registered handler. The result Promise resolves
|
|
72
|
+
* with whatever the handler returned, or rejects with the error. */
|
|
73
|
+
request<A = any, R = any>(method: string, args: A): Promise<R>;
|
|
74
|
+
/** Register the handler for `method`. Only one handler per method —
|
|
75
|
+
* registering twice throws on the second call. Returns a dispose
|
|
76
|
+
* function that unregisters. */
|
|
77
|
+
register<A = any, R = any>(method: string, handler: MethodHandler<A, R>): Dispose;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ── In-process implementation ─────────────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
/** Single-process bus. Both sides of every call share a JavaScript heap.
|
|
83
|
+
* Used in tests, in Android (single-threaded JS), and on whichever side of
|
|
84
|
+
* a worker boundary is the host.
|
|
85
|
+
*
|
|
86
|
+
* publish() defers subscriber dispatch to the next microtask so a
|
|
87
|
+
* publisher's stack doesn't deepen indefinitely if a handler republishes
|
|
88
|
+
* on the same topic. */
|
|
89
|
+
export class InProcessBus implements Bus {
|
|
90
|
+
private topics = new Map<string, Set<TopicHandler>>();
|
|
91
|
+
private methods = new Map<string, MethodHandler>();
|
|
92
|
+
|
|
93
|
+
publish<P>(topic: string, payload: P): void {
|
|
94
|
+
const handlers = this.topics.get(topic);
|
|
95
|
+
if (!handlers || handlers.size === 0) return;
|
|
96
|
+
// Snapshot so a subscriber that disposes itself during dispatch
|
|
97
|
+
// doesn't perturb iteration of the live set.
|
|
98
|
+
const snapshot = Array.from(handlers);
|
|
99
|
+
queueMicrotask(() => {
|
|
100
|
+
for (const h of snapshot) {
|
|
101
|
+
try { h(payload); } catch (e: any) { console.error(`[bus] subscriber for "${topic}" threw: ${e?.message || e}`); }
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
subscribe<P>(topic: string, handler: TopicHandler<P>): Dispose {
|
|
107
|
+
let set = this.topics.get(topic);
|
|
108
|
+
if (!set) { set = new Set(); this.topics.set(topic, set); }
|
|
109
|
+
set.add(handler as TopicHandler);
|
|
110
|
+
return () => { set!.delete(handler as TopicHandler); };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async request<A, R>(method: string, args: A): Promise<R> {
|
|
114
|
+
const handler = this.methods.get(method);
|
|
115
|
+
if (!handler) throw new Error(`bus: no handler registered for "${method}"`);
|
|
116
|
+
// Always async — a handler that throws synchronously would otherwise
|
|
117
|
+
// bypass the Promise rejection path. Wrap in Promise.resolve().
|
|
118
|
+
return Promise.resolve().then(() => handler(args)) as Promise<R>;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
register<A, R>(method: string, handler: MethodHandler<A, R>): Dispose {
|
|
122
|
+
if (this.methods.has(method)) {
|
|
123
|
+
throw new Error(`bus: "${method}" is already registered`);
|
|
124
|
+
}
|
|
125
|
+
this.methods.set(method, handler as MethodHandler);
|
|
126
|
+
return () => { this.methods.delete(method); };
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ── Worker / MessagePort implementation ───────────────────────────────────
|
|
131
|
+
|
|
132
|
+
/** Anything that quacks like a MessagePort — Node's `worker_threads.parentPort`
|
|
133
|
+
* and `Worker` instances, browser's `MessagePort`, etc. */
|
|
134
|
+
export interface MessagePortLike {
|
|
135
|
+
postMessage(value: any): void;
|
|
136
|
+
on?(event: "message", handler: (value: any) => void): void; // Node
|
|
137
|
+
addEventListener?(event: "message", handler: (e: MessageEvent) => void): void; // browser
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
interface WireMessage {
|
|
141
|
+
kind: "publish" | "request" | "reply" | "subscribe-add" | "subscribe-remove" | "register-add" | "register-remove";
|
|
142
|
+
topic?: string;
|
|
143
|
+
method?: string;
|
|
144
|
+
payload?: any;
|
|
145
|
+
args?: any;
|
|
146
|
+
result?: any;
|
|
147
|
+
error?: string;
|
|
148
|
+
/** Correlation id for request/reply. */
|
|
149
|
+
id?: number;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Cross-thread bus. Each side instantiates a `WorkerBus` over its end of a
|
|
153
|
+
* `MessageChannel`. The two sides are mirror images: subscribing on side A
|
|
154
|
+
* causes events published on side B to flow over the wire to A. Requests
|
|
155
|
+
* initiated on side A are served by whoever registered the method on side B.
|
|
156
|
+
*
|
|
157
|
+
* Topology assumed by this implementation: exactly two endpoints. Multi-
|
|
158
|
+
* party busses (>2 peers) need a hub topology and aren't covered here. */
|
|
159
|
+
export class WorkerBus implements Bus {
|
|
160
|
+
private port: MessagePortLike;
|
|
161
|
+
private localSubs = new Map<string, Set<TopicHandler>>();
|
|
162
|
+
/** Methods registered on THIS side. Requests arriving over the wire
|
|
163
|
+
* for these methods are dispatched locally. */
|
|
164
|
+
private localMethods = new Map<string, MethodHandler>();
|
|
165
|
+
/** Pending requests we sent to the peer, awaiting reply. */
|
|
166
|
+
private pending = new Map<number, { resolve: (r: any) => void; reject: (e: any) => void }>();
|
|
167
|
+
private nextId = 1;
|
|
168
|
+
|
|
169
|
+
constructor(port: MessagePortLike) {
|
|
170
|
+
this.port = port;
|
|
171
|
+
const onMessage = (raw: any): void => {
|
|
172
|
+
const msg = (raw && typeof raw === "object" && "data" in raw) ? (raw as MessageEvent).data : raw;
|
|
173
|
+
this.handleIncoming(msg as WireMessage);
|
|
174
|
+
};
|
|
175
|
+
if (typeof port.on === "function") port.on("message", onMessage);
|
|
176
|
+
else if (typeof port.addEventListener === "function") port.addEventListener("message", onMessage);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
publish<P>(topic: string, payload: P): void {
|
|
180
|
+
// Local subscribers fire immediately (same-side publish/subscribe is
|
|
181
|
+
// allowed). The peer receives a publish message and dispatches there.
|
|
182
|
+
const local = this.localSubs.get(topic);
|
|
183
|
+
if (local && local.size > 0) {
|
|
184
|
+
const snapshot = Array.from(local);
|
|
185
|
+
queueMicrotask(() => {
|
|
186
|
+
for (const h of snapshot) {
|
|
187
|
+
try { h(payload); } catch (e: any) { console.error(`[bus] subscriber for "${topic}" threw: ${e?.message || e}`); }
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
this.port.postMessage({ kind: "publish", topic, payload } as WireMessage);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
subscribe<P>(topic: string, handler: TopicHandler<P>): Dispose {
|
|
195
|
+
let set = this.localSubs.get(topic);
|
|
196
|
+
if (!set) { set = new Set(); this.localSubs.set(topic, set); }
|
|
197
|
+
set.add(handler as TopicHandler);
|
|
198
|
+
return () => { set!.delete(handler as TopicHandler); };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async request<A, R>(method: string, args: A): Promise<R> {
|
|
202
|
+
// Prefer local handler if one is registered on this side (allows a
|
|
203
|
+
// factor to bundle with the bus for in-process testing).
|
|
204
|
+
const local = this.localMethods.get(method);
|
|
205
|
+
if (local) return Promise.resolve().then(() => local(args)) as Promise<R>;
|
|
206
|
+
const id = this.nextId++;
|
|
207
|
+
return new Promise<R>((resolve, reject) => {
|
|
208
|
+
this.pending.set(id, { resolve, reject });
|
|
209
|
+
this.port.postMessage({ kind: "request", method, args, id } as WireMessage);
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
register<A, R>(method: string, handler: MethodHandler<A, R>): Dispose {
|
|
214
|
+
if (this.localMethods.has(method)) {
|
|
215
|
+
throw new Error(`bus: "${method}" is already registered`);
|
|
216
|
+
}
|
|
217
|
+
this.localMethods.set(method, handler as MethodHandler);
|
|
218
|
+
return () => { this.localMethods.delete(method); };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
private handleIncoming(msg: WireMessage): void {
|
|
222
|
+
if (!msg || typeof msg !== "object") return;
|
|
223
|
+
switch (msg.kind) {
|
|
224
|
+
case "publish": {
|
|
225
|
+
if (typeof msg.topic !== "string") return;
|
|
226
|
+
const subs = this.localSubs.get(msg.topic);
|
|
227
|
+
if (!subs || subs.size === 0) return;
|
|
228
|
+
const snapshot = Array.from(subs);
|
|
229
|
+
queueMicrotask(() => {
|
|
230
|
+
for (const h of snapshot) {
|
|
231
|
+
try { h(msg.payload); } catch (e: any) { console.error(`[bus] subscriber for "${msg.topic}" threw: ${e?.message || e}`); }
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
case "request": {
|
|
237
|
+
if (typeof msg.method !== "string" || typeof msg.id !== "number") return;
|
|
238
|
+
const handler = this.localMethods.get(msg.method);
|
|
239
|
+
if (!handler) {
|
|
240
|
+
this.port.postMessage({ kind: "reply", id: msg.id, error: `no handler registered for "${msg.method}"` } as WireMessage);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
Promise.resolve()
|
|
244
|
+
.then(() => handler(msg.args))
|
|
245
|
+
.then(result => this.port.postMessage({ kind: "reply", id: msg.id, result } as WireMessage))
|
|
246
|
+
.catch(e => this.port.postMessage({ kind: "reply", id: msg.id, error: e?.message || String(e) } as WireMessage));
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
case "reply": {
|
|
250
|
+
if (typeof msg.id !== "number") return;
|
|
251
|
+
const entry = this.pending.get(msg.id);
|
|
252
|
+
if (!entry) return;
|
|
253
|
+
this.pending.delete(msg.id);
|
|
254
|
+
if (msg.error) entry.reject(new Error(msg.error));
|
|
255
|
+
else entry.resolve(msg.result);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
// subscribe-add/remove/register-add/remove are reserved for a
|
|
259
|
+
// future optimisation where the bus only forwards events the
|
|
260
|
+
// peer is interested in. Today every publish crosses the wire;
|
|
261
|
+
// subscribers filter locally. Cheap enough at our volumes.
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// ── Store-event bus ───────────────────────────────────────────────────────
|
|
267
|
+
//
|
|
268
|
+
// Typed, batch-coalescing pub/sub specifically for mailx-store mutations.
|
|
269
|
+
// Lives in this package (zero deps) so both `mailx-store` (desktop, Node +
|
|
270
|
+
// better-sqlite3) and `mailx-store-web` (Android/browser, sql.js + IDB)
|
|
271
|
+
// import the SAME bus. That is the load-bearing property for code sharing
|
|
272
|
+
// across platforms — a Store event published in either implementation is
|
|
273
|
+
// indistinguishable to subscribers.
|
|
274
|
+
export { StoreBus, storeBus } from "./store-events.js";
|
|
275
|
+
export type { StoreEvent, StoreEventKind, StoreEventHandler } from "./store-events.js";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bobfrankston/mailx-bus",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"types": "index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"release": "npmglobalize"
|
|
10
|
+
},
|
|
11
|
+
"license": "ISC",
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "https://github.com/BobFrankston/mailx-bus.git"
|
|
15
|
+
},
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StoreBus — typed pub/sub for Store events.
|
|
3
|
+
*
|
|
4
|
+
* Postmessage-style: subscribers register a topic string + handler; publishers
|
|
5
|
+
* emit a {topic, kind, ...} object. The exact same shape works inside Node,
|
|
6
|
+
* across worker_threads (postMessage), and across the WebView⇄host boundary
|
|
7
|
+
* (also postMessage). That's the load-bearing property: one mental model from
|
|
8
|
+
* DB write to UI re-render, on any platform.
|
|
9
|
+
*
|
|
10
|
+
* Topics
|
|
11
|
+
* ──────
|
|
12
|
+
* `message:<uuid>` — a specific message changed (flags, body, removal)
|
|
13
|
+
* `folder:<id>` — anything inside folder <id> changed; the bus fans
|
|
14
|
+
* out per-message events to the parent folder topic
|
|
15
|
+
* automatically (see fanOutToFolder())
|
|
16
|
+
* `account:<id>` — account-scoped event (sync status, auth, quota)
|
|
17
|
+
* `*` — wildcard; receives every event. Use for the IPC
|
|
18
|
+
* forwarder that pushes to the WebView, and for
|
|
19
|
+
* diagnostics. Avoid in normal UI subscribers.
|
|
20
|
+
*
|
|
21
|
+
* Batching
|
|
22
|
+
* ────────
|
|
23
|
+
* Bulk writes (sync round inserts 200 envelopes) would emit 200 events. The
|
|
24
|
+
* `withBatch(fn)` wrapper buffers events fired during fn() and coalesces
|
|
25
|
+
* per-topic into one {kind:"batch", changedUuids, summary} event at the end.
|
|
26
|
+
* Subscribers either iterate `changedUuids` or treat batch as "rerender this
|
|
27
|
+
* topic." Nested withBatch() is supported; inner scopes just append to the
|
|
28
|
+
* outer's buffer.
|
|
29
|
+
*/
|
|
30
|
+
export type StoreEventKind = "messageInserted" | "messageUpdated" | "messageRemoved" | "messageMoved" | "flagsChanged" | "bodyAvailable" | "bodyFetchError" | "folderCountsChanged" | "draftSaved" | "draftSaveDeferred" | "batch";
|
|
31
|
+
export interface StoreEvent {
|
|
32
|
+
topic: string;
|
|
33
|
+
kind: StoreEventKind;
|
|
34
|
+
accountId?: string;
|
|
35
|
+
folderId?: number;
|
|
36
|
+
targetFolderId?: number;
|
|
37
|
+
uid?: number;
|
|
38
|
+
msgUuid?: string;
|
|
39
|
+
flags?: string[];
|
|
40
|
+
error?: string;
|
|
41
|
+
/** Set on `batch` events. UUIDs of every message touched in the scope. */
|
|
42
|
+
changedUuids?: string[];
|
|
43
|
+
/** Set on `batch` events. */
|
|
44
|
+
summary?: {
|
|
45
|
+
inserted: number;
|
|
46
|
+
updated: number;
|
|
47
|
+
deleted: number;
|
|
48
|
+
};
|
|
49
|
+
/** Arbitrary additional payload for kind-specific data the typed fields
|
|
50
|
+
* above don't cover (e.g. flag-set details, body path). Keep small. */
|
|
51
|
+
[extra: string]: unknown;
|
|
52
|
+
}
|
|
53
|
+
export type StoreEventHandler = (event: StoreEvent) => void;
|
|
54
|
+
export declare class StoreBus {
|
|
55
|
+
private subscribers;
|
|
56
|
+
private batchDepth;
|
|
57
|
+
private buffer;
|
|
58
|
+
subscribe(topic: string, handler: StoreEventHandler): () => void;
|
|
59
|
+
/** Publish an event. If a folderId is set and the topic is a message
|
|
60
|
+
* topic, the bus also publishes a copy to the parent folder topic so
|
|
61
|
+
* list views (subscribed to folder:<id>) wake without subscribing to
|
|
62
|
+
* every message individually. */
|
|
63
|
+
publish(event: StoreEvent): void;
|
|
64
|
+
/** Run `fn` with publishes buffered. On exit, coalesces per-topic and
|
|
65
|
+
* delivers either the single event (when only one fired for that topic)
|
|
66
|
+
* or a synthetic batch event summarizing the changes. */
|
|
67
|
+
withBatch<T>(fn: () => T): T;
|
|
68
|
+
private flush;
|
|
69
|
+
private deliver;
|
|
70
|
+
/** When a message-topic event fires and carries a folderId, generate
|
|
71
|
+
* the parallel folder-topic event so folder subscribers don't have to
|
|
72
|
+
* also subscribe to every message individually. Returns null when no
|
|
73
|
+
* fan-out applies. */
|
|
74
|
+
private fanOutToFolder;
|
|
75
|
+
}
|
|
76
|
+
/** Singleton bus shared by every Store consumer in the process. Workers
|
|
77
|
+
* get their own; the worker boundary serializes events via postMessage. */
|
|
78
|
+
export declare const storeBus: StoreBus;
|
|
79
|
+
//# sourceMappingURL=store-events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store-events.d.ts","sourceRoot":"","sources":["store-events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,MAAM,MAAM,cAAc,GACpB,iBAAiB,GACjB,gBAAgB,GAChB,gBAAgB,GAChB,cAAc,GACd,cAAc,GACd,eAAe,GACf,gBAAgB,GAChB,qBAAqB,GACrB,YAAY,GACZ,mBAAmB,GACnB,OAAO,CAAC;AAEd,MAAM,WAAW,UAAU;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,cAAc,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0EAA0E;IAC1E,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,6BAA6B;IAC7B,OAAO,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACjE;4EACwE;IACxE,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;CAC5B;AAED,MAAM,MAAM,iBAAiB,GAAG,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;AAE5D,qBAAa,QAAQ;IACjB,OAAO,CAAC,WAAW,CAA6C;IAChE,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,MAAM,CAAoB;IAElC,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,GAAG,MAAM,IAAI;IAgBhE;;;sCAGkC;IAClC,OAAO,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;IAahC;;8DAE0D;IAC1D,SAAS,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC;IAU5B,OAAO,CAAC,KAAK;IAwBb,OAAO,CAAC,OAAO;IAWf;;;2BAGuB;IACvB,OAAO,CAAC,cAAc;CAMzB;AAED;4EAC4E;AAC5E,eAAO,MAAM,QAAQ,UAAiB,CAAC"}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StoreBus — typed pub/sub for Store events.
|
|
3
|
+
*
|
|
4
|
+
* Postmessage-style: subscribers register a topic string + handler; publishers
|
|
5
|
+
* emit a {topic, kind, ...} object. The exact same shape works inside Node,
|
|
6
|
+
* across worker_threads (postMessage), and across the WebView⇄host boundary
|
|
7
|
+
* (also postMessage). That's the load-bearing property: one mental model from
|
|
8
|
+
* DB write to UI re-render, on any platform.
|
|
9
|
+
*
|
|
10
|
+
* Topics
|
|
11
|
+
* ──────
|
|
12
|
+
* `message:<uuid>` — a specific message changed (flags, body, removal)
|
|
13
|
+
* `folder:<id>` — anything inside folder <id> changed; the bus fans
|
|
14
|
+
* out per-message events to the parent folder topic
|
|
15
|
+
* automatically (see fanOutToFolder())
|
|
16
|
+
* `account:<id>` — account-scoped event (sync status, auth, quota)
|
|
17
|
+
* `*` — wildcard; receives every event. Use for the IPC
|
|
18
|
+
* forwarder that pushes to the WebView, and for
|
|
19
|
+
* diagnostics. Avoid in normal UI subscribers.
|
|
20
|
+
*
|
|
21
|
+
* Batching
|
|
22
|
+
* ────────
|
|
23
|
+
* Bulk writes (sync round inserts 200 envelopes) would emit 200 events. The
|
|
24
|
+
* `withBatch(fn)` wrapper buffers events fired during fn() and coalesces
|
|
25
|
+
* per-topic into one {kind:"batch", changedUuids, summary} event at the end.
|
|
26
|
+
* Subscribers either iterate `changedUuids` or treat batch as "rerender this
|
|
27
|
+
* topic." Nested withBatch() is supported; inner scopes just append to the
|
|
28
|
+
* outer's buffer.
|
|
29
|
+
*/
|
|
30
|
+
export class StoreBus {
|
|
31
|
+
subscribers = new Map();
|
|
32
|
+
batchDepth = 0;
|
|
33
|
+
buffer = [];
|
|
34
|
+
subscribe(topic, handler) {
|
|
35
|
+
let set = this.subscribers.get(topic);
|
|
36
|
+
if (!set) {
|
|
37
|
+
set = new Set();
|
|
38
|
+
this.subscribers.set(topic, set);
|
|
39
|
+
}
|
|
40
|
+
set.add(handler);
|
|
41
|
+
return () => {
|
|
42
|
+
const s = this.subscribers.get(topic);
|
|
43
|
+
if (s) {
|
|
44
|
+
s.delete(handler);
|
|
45
|
+
if (s.size === 0)
|
|
46
|
+
this.subscribers.delete(topic);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/** Publish an event. If a folderId is set and the topic is a message
|
|
51
|
+
* topic, the bus also publishes a copy to the parent folder topic so
|
|
52
|
+
* list views (subscribed to folder:<id>) wake without subscribing to
|
|
53
|
+
* every message individually. */
|
|
54
|
+
publish(event) {
|
|
55
|
+
if (this.batchDepth > 0) {
|
|
56
|
+
this.buffer.push(event);
|
|
57
|
+
// Folder fan-out also buffers; coalesces at flush time.
|
|
58
|
+
const fanned = this.fanOutToFolder(event);
|
|
59
|
+
if (fanned)
|
|
60
|
+
this.buffer.push(fanned);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
this.deliver(event);
|
|
64
|
+
const fanned = this.fanOutToFolder(event);
|
|
65
|
+
if (fanned)
|
|
66
|
+
this.deliver(fanned);
|
|
67
|
+
}
|
|
68
|
+
/** Run `fn` with publishes buffered. On exit, coalesces per-topic and
|
|
69
|
+
* delivers either the single event (when only one fired for that topic)
|
|
70
|
+
* or a synthetic batch event summarizing the changes. */
|
|
71
|
+
withBatch(fn) {
|
|
72
|
+
this.batchDepth++;
|
|
73
|
+
try {
|
|
74
|
+
return fn();
|
|
75
|
+
}
|
|
76
|
+
finally {
|
|
77
|
+
this.batchDepth--;
|
|
78
|
+
if (this.batchDepth === 0)
|
|
79
|
+
this.flush();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
flush() {
|
|
83
|
+
const buf = this.buffer;
|
|
84
|
+
this.buffer = [];
|
|
85
|
+
if (buf.length === 0)
|
|
86
|
+
return;
|
|
87
|
+
const byTopic = new Map();
|
|
88
|
+
for (const e of buf) {
|
|
89
|
+
let arr = byTopic.get(e.topic);
|
|
90
|
+
if (!arr) {
|
|
91
|
+
arr = [];
|
|
92
|
+
byTopic.set(e.topic, arr);
|
|
93
|
+
}
|
|
94
|
+
arr.push(e);
|
|
95
|
+
}
|
|
96
|
+
for (const [topic, events] of byTopic) {
|
|
97
|
+
if (events.length === 1) {
|
|
98
|
+
this.deliver(events[0]);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const summary = { inserted: 0, updated: 0, deleted: 0 };
|
|
102
|
+
const uuids = new Set();
|
|
103
|
+
for (const e of events) {
|
|
104
|
+
if (e.kind === "messageInserted")
|
|
105
|
+
summary.inserted++;
|
|
106
|
+
else if (e.kind === "messageRemoved")
|
|
107
|
+
summary.deleted++;
|
|
108
|
+
else
|
|
109
|
+
summary.updated++;
|
|
110
|
+
if (e.msgUuid)
|
|
111
|
+
uuids.add(e.msgUuid);
|
|
112
|
+
}
|
|
113
|
+
this.deliver({ topic, kind: "batch", changedUuids: [...uuids], summary });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
deliver(event) {
|
|
117
|
+
const exact = this.subscribers.get(event.topic);
|
|
118
|
+
if (exact)
|
|
119
|
+
for (const h of exact) {
|
|
120
|
+
try {
|
|
121
|
+
h(event);
|
|
122
|
+
}
|
|
123
|
+
catch (e) {
|
|
124
|
+
console.error("[store-bus]", e);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const wild = this.subscribers.get("*");
|
|
128
|
+
if (wild)
|
|
129
|
+
for (const h of wild) {
|
|
130
|
+
try {
|
|
131
|
+
h(event);
|
|
132
|
+
}
|
|
133
|
+
catch (e) {
|
|
134
|
+
console.error("[store-bus]", e);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/** When a message-topic event fires and carries a folderId, generate
|
|
139
|
+
* the parallel folder-topic event so folder subscribers don't have to
|
|
140
|
+
* also subscribe to every message individually. Returns null when no
|
|
141
|
+
* fan-out applies. */
|
|
142
|
+
fanOutToFolder(event) {
|
|
143
|
+
if (event.kind === "batch")
|
|
144
|
+
return null;
|
|
145
|
+
if (!event.topic.startsWith("message:"))
|
|
146
|
+
return null;
|
|
147
|
+
if (event.folderId == null)
|
|
148
|
+
return null;
|
|
149
|
+
return { ...event, topic: `folder:${event.folderId}` };
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/** Singleton bus shared by every Store consumer in the process. Workers
|
|
153
|
+
* get their own; the worker boundary serializes events via postMessage. */
|
|
154
|
+
export const storeBus = new StoreBus();
|
|
155
|
+
//# sourceMappingURL=store-events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store-events.js","sourceRoot":"","sources":["store-events.ts"],"names":[],"mappings":"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"}
|