feat(tasks,server,web): bridge outbox events to the SSE bus (U3)
OutboxRelay gains a `mirror` option: listed types are claimed even without a task route and, after the claim transaction commits, published in order to the realtime bus (publish failures are logged, delivery state unaffected). The worker mirrors feed.*, decision.*, download.*, organization.* and subscription.* (feed.due excluded) with their business-id payloads, so the home page, inbox, task stream and feed tables finally receive them. Feed tables refresh on feed.fetched / feed.health_changed; the task stream also listens to feed and decision events. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
04401309fb
commit
615917bb45
6 files changed
+136
-6
No files matched your search
@@ -127,4 +127,40 @@ describe.skipIf(!hasPg)("OutboxRelay", () => {
|
||||
}>`SELECT status FROM outbox_event WHERE type = 'echo.requested' AND published_at IS NULL`.execute(db);
|
||||
expect(dead.rows).toEqual([{ status: "dead" }]);
|
||||
});
|
||||
|
||||
it("mirror:投递成功后按顺序镜像到实时总线;只镜像不路由的 type 也认领,失败的不镜像", async () => {
|
||||
await sql`DELETE FROM outbox_event`.execute(db);
|
||||
await sql`INSERT INTO outbox_event (type, payload, at) VALUES
|
||||
('mirror.only', '{"recordId":"r1"}'::jsonb, now() - interval '3 second'),
|
||||
('echo.requested', '{"n":1001}'::jsonb, now() - interval '2 second'),
|
||||
('echo.requested', '{"n":"bad"}'::jsonb, now() - interval '1 second'),
|
||||
('not.mirrored', '{}'::jsonb, now())`.execute(db);
|
||||
const seen: { type: string; payload: unknown }[] = [];
|
||||
const relay = new OutboxRelay({
|
||||
db,
|
||||
engine,
|
||||
routes: { "echo.requested": (ev) => ({ task: "echo", input: ev.payload }) },
|
||||
logSink: () => {},
|
||||
mirror: {
|
||||
types: ["mirror.only", "echo.requested"],
|
||||
publish: (ev) => {
|
||||
seen.push({ type: ev.type, payload: ev.payload });
|
||||
if (ev.type === "mirror.only") throw new Error("bus down"); // 推送失败不影响投递
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(await relay.pollOnce()).toEqual({ published: 2, failed: 1 });
|
||||
expect(seen).toEqual([
|
||||
{ type: "mirror.only", payload: { recordId: "r1" } },
|
||||
{ type: "echo.requested", payload: { n: 1001 } },
|
||||
]);
|
||||
const status = await sql<{ type: string; status: string }>`
|
||||
SELECT type, status FROM outbox_event ORDER BY at`.execute(db);
|
||||
expect(status.rows.map((r) => `${r.type}:${r.status}`)).toEqual([
|
||||
"mirror.only:dispatched",
|
||||
"echo.requested:dispatched",
|
||||
"echo.requested:pending",
|
||||
"not.mirrored:pending",
|
||||
]);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user