hazo_notify 3.1.0 → 5.0.0
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/README.md +122 -17
- package/config/hazo_notify_config.ini +37 -0
- package/dist/components/notification_banner/index.d.ts +27 -0
- package/dist/components/notification_banner/index.d.ts.map +1 -0
- package/dist/components/notification_banner/index.js +42 -0
- package/dist/components/notification_banner/index.js.map +1 -0
- package/dist/components/notification_bell/index.d.ts +56 -0
- package/dist/components/notification_bell/index.d.ts.map +1 -0
- package/dist/components/notification_bell/index.js +73 -0
- package/dist/components/notification_bell/index.js.map +1 -0
- package/dist/components/template_manager/template_editor.js +3 -3
- package/dist/components/template_manager/template_editor.js.map +1 -1
- package/dist/components/template_manager/template_manager_admin.d.ts.map +1 -1
- package/dist/components/template_manager/template_manager_admin.js +3 -4
- package/dist/components/template_manager/template_manager_admin.js.map +1 -1
- package/dist/lib/adapters/email/adapter.d.ts +49 -0
- package/dist/lib/adapters/email/adapter.d.ts.map +1 -0
- package/dist/lib/adapters/email/adapter.js +87 -0
- package/dist/lib/adapters/email/adapter.js.map +1 -0
- package/dist/lib/adapters/email/config.d.ts +15 -0
- package/dist/lib/adapters/email/config.d.ts.map +1 -0
- package/dist/lib/{emailer/emailer.js → adapters/email/config.js} +7 -113
- package/dist/lib/adapters/email/config.js.map +1 -0
- package/dist/lib/adapters/email/envelope.d.ts +29 -0
- package/dist/lib/adapters/email/envelope.d.ts.map +1 -0
- package/dist/lib/adapters/email/envelope.js +46 -0
- package/dist/lib/adapters/email/envelope.js.map +1 -0
- package/dist/lib/adapters/email/index.d.ts +12 -0
- package/dist/lib/adapters/email/index.d.ts.map +1 -0
- package/dist/lib/adapters/email/index.js +11 -0
- package/dist/lib/adapters/email/index.js.map +1 -0
- package/dist/lib/adapters/email/providers/index.d.ts.map +1 -0
- package/dist/lib/adapters/email/providers/index.js.map +1 -0
- package/dist/lib/adapters/email/providers/pop3_provider.d.ts.map +1 -0
- package/dist/lib/adapters/email/providers/pop3_provider.js.map +1 -0
- package/dist/lib/adapters/email/providers/smtp_provider.d.ts.map +1 -0
- package/dist/lib/adapters/email/providers/smtp_provider.js.map +1 -0
- package/dist/lib/adapters/email/providers/zeptomail_provider.d.ts.map +1 -0
- package/dist/lib/adapters/email/providers/zeptomail_provider.js.map +1 -0
- package/dist/lib/{emailer/emailer.d.ts → adapters/email/send_email.d.ts} +9 -10
- package/dist/lib/adapters/email/send_email.d.ts.map +1 -0
- package/dist/lib/adapters/email/send_email.js +122 -0
- package/dist/lib/adapters/email/send_email.js.map +1 -0
- package/dist/lib/adapters/email/types.d.ts.map +1 -0
- package/dist/lib/adapters/email/types.js.map +1 -0
- package/dist/lib/adapters/email/utils/constants.d.ts.map +1 -0
- package/dist/lib/adapters/email/utils/constants.js.map +1 -0
- package/dist/lib/adapters/email/utils/index.d.ts.map +1 -0
- package/dist/lib/adapters/email/utils/index.js.map +1 -0
- package/dist/lib/adapters/email/utils/logger.d.ts.map +1 -0
- package/dist/lib/adapters/email/utils/logger.js.map +1 -0
- package/dist/lib/adapters/email/utils/validation.d.ts.map +1 -0
- package/dist/lib/adapters/email/utils/validation.js.map +1 -0
- package/dist/lib/adapters/telegram/adapter.d.ts +25 -0
- package/dist/lib/adapters/telegram/adapter.d.ts.map +1 -0
- package/dist/lib/adapters/telegram/adapter.js +75 -0
- package/dist/lib/adapters/telegram/adapter.js.map +1 -0
- package/dist/lib/adapters/telegram/config.d.ts +22 -0
- package/dist/lib/adapters/telegram/config.d.ts.map +1 -0
- package/dist/lib/adapters/telegram/config.js +60 -0
- package/dist/lib/adapters/telegram/config.js.map +1 -0
- package/dist/lib/adapters/telegram/index.d.ts +8 -0
- package/dist/lib/adapters/telegram/index.d.ts.map +1 -0
- package/dist/lib/adapters/telegram/index.js +5 -0
- package/dist/lib/adapters/telegram/index.js.map +1 -0
- package/dist/lib/adapters/telegram/splitter.d.ts +7 -0
- package/dist/lib/adapters/telegram/splitter.d.ts.map +1 -0
- package/dist/lib/adapters/telegram/splitter.js +47 -0
- package/dist/lib/adapters/telegram/splitter.js.map +1 -0
- package/dist/lib/adapters/telegram/transport.d.ts +31 -0
- package/dist/lib/adapters/telegram/transport.d.ts.map +1 -0
- package/dist/lib/adapters/telegram/transport.js +112 -0
- package/dist/lib/adapters/telegram/transport.js.map +1 -0
- package/dist/lib/api/banner.d.ts +41 -0
- package/dist/lib/api/banner.d.ts.map +1 -0
- package/dist/lib/api/banner.js +53 -0
- package/dist/lib/api/banner.js.map +1 -0
- package/dist/lib/api/inbox.d.ts +33 -0
- package/dist/lib/api/inbox.d.ts.map +1 -0
- package/dist/lib/api/inbox.js +52 -0
- package/dist/lib/api/inbox.js.map +1 -0
- package/dist/lib/channels/index.d.ts +3 -0
- package/dist/lib/channels/index.d.ts.map +1 -0
- package/dist/lib/channels/index.js +3 -0
- package/dist/lib/channels/index.js.map +1 -0
- package/dist/lib/channels/registry.d.ts +7 -0
- package/dist/lib/channels/registry.d.ts.map +1 -0
- package/dist/lib/channels/registry.js +24 -0
- package/dist/lib/channels/registry.js.map +1 -0
- package/dist/lib/channels/types.d.ts +82 -0
- package/dist/lib/channels/types.d.ts.map +1 -0
- package/dist/lib/channels/types.js +8 -0
- package/dist/lib/channels/types.js.map +1 -0
- package/dist/lib/dispatcher/batch-key.d.ts +15 -0
- package/dist/lib/dispatcher/batch-key.d.ts.map +1 -0
- package/dist/lib/dispatcher/batch-key.js +19 -0
- package/dist/lib/dispatcher/batch-key.js.map +1 -0
- package/dist/lib/dispatcher/index.d.ts +4 -0
- package/dist/lib/dispatcher/index.d.ts.map +1 -0
- package/dist/lib/dispatcher/index.js +73 -0
- package/dist/lib/dispatcher/index.js.map +1 -0
- package/dist/lib/inbox/connection.d.ts +19 -0
- package/dist/lib/inbox/connection.d.ts.map +1 -0
- package/dist/lib/inbox/connection.js +37 -0
- package/dist/lib/inbox/connection.js.map +1 -0
- package/dist/lib/inbox/dialects/index.d.ts +10 -0
- package/dist/lib/inbox/dialects/index.d.ts.map +1 -0
- package/dist/lib/inbox/dialects/index.js +13 -0
- package/dist/lib/inbox/dialects/index.js.map +1 -0
- package/dist/lib/inbox/dialects/postgres.d.ts +3 -0
- package/dist/lib/inbox/dialects/postgres.d.ts.map +1 -0
- package/dist/lib/inbox/dialects/postgres.js +110 -0
- package/dist/lib/inbox/dialects/postgres.js.map +1 -0
- package/dist/lib/inbox/dialects/sqlite.d.ts +3 -0
- package/dist/lib/inbox/dialects/sqlite.d.ts.map +1 -0
- package/dist/lib/inbox/dialects/sqlite.js +120 -0
- package/dist/lib/inbox/dialects/sqlite.js.map +1 -0
- package/dist/lib/inbox/dialects/types.d.ts +53 -0
- package/dist/lib/inbox/dialects/types.d.ts.map +1 -0
- package/dist/lib/inbox/dialects/types.js +2 -0
- package/dist/lib/inbox/dialects/types.js.map +1 -0
- package/dist/lib/inbox/index.d.ts +9 -0
- package/dist/lib/inbox/index.d.ts.map +1 -0
- package/dist/lib/inbox/index.js +6 -0
- package/dist/lib/inbox/index.js.map +1 -0
- package/dist/lib/inbox/storage.d.ts +142 -0
- package/dist/lib/inbox/storage.d.ts.map +1 -0
- package/dist/lib/inbox/storage.js +345 -0
- package/dist/lib/inbox/storage.js.map +1 -0
- package/dist/lib/inbox/types.d.ts +94 -0
- package/dist/lib/inbox/types.d.ts.map +1 -0
- package/dist/lib/inbox/types.js +2 -0
- package/dist/lib/inbox/types.js.map +1 -0
- package/dist/lib/inbox/worker/cleanup.d.ts +14 -0
- package/dist/lib/inbox/worker/cleanup.d.ts.map +1 -0
- package/dist/lib/inbox/worker/cleanup.js +37 -0
- package/dist/lib/inbox/worker/cleanup.js.map +1 -0
- package/dist/lib/inbox/worker/flush.d.ts +31 -0
- package/dist/lib/inbox/worker/flush.d.ts.map +1 -0
- package/dist/lib/inbox/worker/flush.js +97 -0
- package/dist/lib/inbox/worker/flush.js.map +1 -0
- package/dist/lib/inbox/worker/render.d.ts +23 -0
- package/dist/lib/inbox/worker/render.d.ts.map +1 -0
- package/dist/lib/inbox/worker/render.js +58 -0
- package/dist/lib/inbox/worker/render.js.map +1 -0
- package/dist/lib/inbox/worker/start.d.ts +29 -0
- package/dist/lib/inbox/worker/start.d.ts.map +1 -0
- package/dist/lib/inbox/worker/start.js +100 -0
- package/dist/lib/inbox/worker/start.js.map +1 -0
- package/dist/lib/index.d.ts +1 -6
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +4 -6
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/jobs/handler.d.ts +25 -0
- package/dist/lib/jobs/handler.d.ts.map +1 -0
- package/dist/lib/jobs/handler.js +36 -0
- package/dist/lib/jobs/handler.js.map +1 -0
- package/dist/lib/jobs/index.d.ts +16 -0
- package/dist/lib/jobs/index.d.ts.map +1 -0
- package/dist/lib/jobs/index.js +13 -0
- package/dist/lib/jobs/index.js.map +1 -0
- package/dist/lib/jobs/submit.d.ts +20 -0
- package/dist/lib/jobs/submit.d.ts.map +1 -0
- package/dist/lib/jobs/submit.js +25 -0
- package/dist/lib/jobs/submit.js.map +1 -0
- package/dist/lib/jobs/types.d.ts +42 -0
- package/dist/lib/jobs/types.d.ts.map +1 -0
- package/dist/lib/jobs/types.js +12 -0
- package/dist/lib/jobs/types.js.map +1 -0
- package/dist/lib/template_manager/db/template_repository.d.ts.map +1 -1
- package/dist/lib/template_manager/db/template_repository.js +16 -31
- package/dist/lib/template_manager/db/template_repository.js.map +1 -1
- package/dist/lib/template_manager/engine/variable_resolver.js +4 -4
- package/dist/lib/template_manager/engine/variable_resolver.js.map +1 -1
- package/dist/lib/template_manager/handlers/index.d.ts.map +1 -1
- package/dist/lib/template_manager/handlers/index.js +2 -4
- package/dist/lib/template_manager/handlers/index.js.map +1 -1
- package/dist/lib/template_manager/seed/sync.d.ts.map +1 -1
- package/dist/lib/template_manager/seed/sync.js +20 -4
- package/dist/lib/template_manager/seed/sync.js.map +1 -1
- package/dist/lib/template_manager/template_manager.d.ts.map +1 -1
- package/dist/lib/template_manager/template_manager.js +67 -26
- package/dist/lib/template_manager/template_manager.js.map +1 -1
- package/dist/lib/template_manager/types.d.ts +59 -9
- package/dist/lib/template_manager/types.d.ts.map +1 -1
- package/dist/lib/template_manager/utils/index.d.ts +1 -1
- package/dist/lib/template_manager/utils/index.d.ts.map +1 -1
- package/dist/lib/template_manager/utils/index.js +1 -1
- package/dist/lib/template_manager/utils/index.js.map +1 -1
- package/dist/lib/template_manager/utils/validation.d.ts +12 -11
- package/dist/lib/template_manager/utils/validation.d.ts.map +1 -1
- package/dist/lib/template_manager/utils/validation.js +15 -22
- package/dist/lib/template_manager/utils/validation.js.map +1 -1
- package/migrations/003_create_notifications_table.sql +48 -0
- package/migrations/004_inbox_upsert_rpc.sql +63 -0
- package/migrations/005_inbox_v4_refactor.pg.sql +53 -0
- package/migrations/005_inbox_v4_refactor.sqlite.sql +30 -0
- package/migrations/006_channel_deliveries.pg.sql +61 -0
- package/migrations/006_channel_deliveries.sqlite.sql +26 -0
- package/migrations/007_templates_bodies_jsonb.pg.sql +17 -0
- package/migrations/007_templates_bodies_jsonb.sqlite.sql +18 -0
- package/package.json +55 -13
- package/dist/lib/emailer/emailer.d.ts.map +0 -1
- package/dist/lib/emailer/emailer.js.map +0 -1
- package/dist/lib/emailer/index.d.ts +0 -12
- package/dist/lib/emailer/index.d.ts.map +0 -1
- package/dist/lib/emailer/index.js +0 -13
- package/dist/lib/emailer/index.js.map +0 -1
- package/dist/lib/emailer/providers/index.d.ts.map +0 -1
- package/dist/lib/emailer/providers/index.js.map +0 -1
- package/dist/lib/emailer/providers/pop3_provider.d.ts.map +0 -1
- package/dist/lib/emailer/providers/pop3_provider.js.map +0 -1
- package/dist/lib/emailer/providers/smtp_provider.d.ts.map +0 -1
- package/dist/lib/emailer/providers/smtp_provider.js.map +0 -1
- package/dist/lib/emailer/providers/zeptomail_provider.d.ts.map +0 -1
- package/dist/lib/emailer/providers/zeptomail_provider.js.map +0 -1
- package/dist/lib/emailer/types.d.ts.map +0 -1
- package/dist/lib/emailer/types.js.map +0 -1
- package/dist/lib/emailer/utils/constants.d.ts.map +0 -1
- package/dist/lib/emailer/utils/constants.js.map +0 -1
- package/dist/lib/emailer/utils/index.d.ts.map +0 -1
- package/dist/lib/emailer/utils/index.js.map +0 -1
- package/dist/lib/emailer/utils/logger.d.ts.map +0 -1
- package/dist/lib/emailer/utils/logger.js.map +0 -1
- package/dist/lib/emailer/utils/validation.d.ts.map +0 -1
- package/dist/lib/emailer/utils/validation.js.map +0 -1
- /package/dist/lib/{emailer → adapters/email}/providers/index.d.ts +0 -0
- /package/dist/lib/{emailer → adapters/email}/providers/index.js +0 -0
- /package/dist/lib/{emailer → adapters/email}/providers/pop3_provider.d.ts +0 -0
- /package/dist/lib/{emailer → adapters/email}/providers/pop3_provider.js +0 -0
- /package/dist/lib/{emailer → adapters/email}/providers/smtp_provider.d.ts +0 -0
- /package/dist/lib/{emailer → adapters/email}/providers/smtp_provider.js +0 -0
- /package/dist/lib/{emailer → adapters/email}/providers/zeptomail_provider.d.ts +0 -0
- /package/dist/lib/{emailer → adapters/email}/providers/zeptomail_provider.js +0 -0
- /package/dist/lib/{emailer → adapters/email}/types.d.ts +0 -0
- /package/dist/lib/{emailer → adapters/email}/types.js +0 -0
- /package/dist/lib/{emailer → adapters/email}/utils/constants.d.ts +0 -0
- /package/dist/lib/{emailer → adapters/email}/utils/constants.js +0 -0
- /package/dist/lib/{emailer → adapters/email}/utils/index.d.ts +0 -0
- /package/dist/lib/{emailer → adapters/email}/utils/index.js +0 -0
- /package/dist/lib/{emailer → adapters/email}/utils/logger.d.ts +0 -0
- /package/dist/lib/{emailer → adapters/email}/utils/logger.js +0 -0
- /package/dist/lib/{emailer → adapters/email}/utils/validation.d.ts +0 -0
- /package/dist/lib/{emailer → adapters/email}/utils/validation.js +0 -0
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import { QueryBuilder, createCrudService } from "hazo_connect/server";
|
|
2
|
+
import { getInboxConnection } from "./connection.js";
|
|
3
|
+
/**
|
|
4
|
+
* Upsert an inbox row using a claim-first strategy:
|
|
5
|
+
*
|
|
6
|
+
* 1. Try to increment an existing open batch that matches `batch_key` and
|
|
7
|
+
* `batch_closed_at`. This preserves the increment-on-conflict semantics of
|
|
8
|
+
* the original Postgres `ON CONFLICT (batch_key) WHERE batch_closed_at IS NULL`
|
|
9
|
+
* partial-index upsert.
|
|
10
|
+
* 2. If no open batch found, insert a fresh row.
|
|
11
|
+
*
|
|
12
|
+
* Race-condition trade-off: if two workers concurrently find no open batch and
|
|
13
|
+
* both insert, both succeed (no unique index guards the SQLite/adapter path).
|
|
14
|
+
* The original Postgres SQL used a partial unique index for true atomicity.
|
|
15
|
+
* For Hazodocs (single-worker), this race window is acceptable; adding retry
|
|
16
|
+
* logic would require error-code parsing not available in hazo_connect.
|
|
17
|
+
*
|
|
18
|
+
* SQLite NULL caveat: the clause-builder emits `col = ?` which SQLite's `=`
|
|
19
|
+
* operator does not use to match NULL (needs IS NULL). In production (Postgres),
|
|
20
|
+
* the behaviour is correct because Postgres's `=` also doesn't match NULL — the
|
|
21
|
+
* partial index `WHERE batch_closed_at IS NULL` handles the constraint there.
|
|
22
|
+
* In SQLite-backed tests use empty string instead of null for batch_closed_at.
|
|
23
|
+
*/
|
|
24
|
+
export async function upsertInboxRow(i) {
|
|
25
|
+
const adapter = getInboxConnection();
|
|
26
|
+
// Try to increment an existing open batch first.
|
|
27
|
+
const claimed = await adapter.claimRows({
|
|
28
|
+
table: "hazo_notify_inbox",
|
|
29
|
+
where: { batch_key: i.batch_key, batch_closed_at: i.batch_closed_at ?? "" },
|
|
30
|
+
set: {
|
|
31
|
+
aggregate_count: { increment: 1 },
|
|
32
|
+
in_app_text: i.in_app_text,
|
|
33
|
+
payload: JSON.stringify(i.payload),
|
|
34
|
+
updated_at: { $now: true },
|
|
35
|
+
},
|
|
36
|
+
returning: ["id"],
|
|
37
|
+
limit: 1,
|
|
38
|
+
});
|
|
39
|
+
if (claimed.length > 0) {
|
|
40
|
+
return { id: claimed[0].id, inserted: false };
|
|
41
|
+
}
|
|
42
|
+
// No open batch — insert a new row.
|
|
43
|
+
const rows = await adapter.query(new QueryBuilder()
|
|
44
|
+
.from("hazo_notify_inbox")
|
|
45
|
+
.returning("id"), "POST", {
|
|
46
|
+
scope_id: i.scope_id,
|
|
47
|
+
user_id: i.user_id,
|
|
48
|
+
event_type: i.event_type,
|
|
49
|
+
subject_id: i.subject_id,
|
|
50
|
+
batch_key: i.batch_key,
|
|
51
|
+
in_app_text: i.in_app_text,
|
|
52
|
+
deep_link: i.deep_link,
|
|
53
|
+
payload: JSON.stringify(i.payload),
|
|
54
|
+
surfaces: JSON.stringify(i.surfaces),
|
|
55
|
+
batch_closed_at: i.batch_closed_at,
|
|
56
|
+
});
|
|
57
|
+
return { id: rows[0].id, inserted: true };
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Upsert a channel delivery row using a two-claim strategy that preserves the
|
|
61
|
+
* original Postgres CASE-WHEN semantic for `flush_after`:
|
|
62
|
+
*
|
|
63
|
+
* 1. Claim an **existing fresh row** (attempt_count = 0) — update both `payload`
|
|
64
|
+
* and `flush_after` (the delivery hasn't been tried yet, so rescheduling is safe).
|
|
65
|
+
* 2. Claim an **existing in-flight row** (attempt_count > 0) — update `payload`
|
|
66
|
+
* only; preserve `flush_after` to avoid resetting an active retry schedule.
|
|
67
|
+
* 3. If neither claim succeeds, no row exists — insert a new one.
|
|
68
|
+
*
|
|
69
|
+
* The two claims are mutually exclusive so at most one succeeds per call.
|
|
70
|
+
*/
|
|
71
|
+
export async function upsertDelivery(i) {
|
|
72
|
+
const adapter = getInboxConnection();
|
|
73
|
+
// Step 1: claim a fresh (not-yet-attempted) row — update payload + flush_after.
|
|
74
|
+
const claimedFresh = await adapter.claimRows({
|
|
75
|
+
table: "hazo_notify_channel_deliveries",
|
|
76
|
+
where: { inbox_id: i.inbox_id, channel_id: i.channel_id, attempt_count: 0 },
|
|
77
|
+
set: { payload: JSON.stringify(i.payload), flush_after: i.flush_after, updated_at: { $now: true } },
|
|
78
|
+
returning: ["id"],
|
|
79
|
+
limit: 1,
|
|
80
|
+
});
|
|
81
|
+
if (claimedFresh.length > 0) {
|
|
82
|
+
return { id: claimedFresh[0].id, inserted: false, attempt_in_flight: false };
|
|
83
|
+
}
|
|
84
|
+
// Step 2: claim an in-flight row (attempt_count > 0) — update payload only,
|
|
85
|
+
// preserve flush_after so the retry schedule is not reset.
|
|
86
|
+
const claimedInFlight = await adapter.claimRows({
|
|
87
|
+
table: "hazo_notify_channel_deliveries",
|
|
88
|
+
where: { inbox_id: i.inbox_id, channel_id: i.channel_id, attempt_count: { gt: 0 } },
|
|
89
|
+
set: { payload: JSON.stringify(i.payload), updated_at: { $now: true } },
|
|
90
|
+
returning: ["id"],
|
|
91
|
+
limit: 1,
|
|
92
|
+
});
|
|
93
|
+
if (claimedInFlight.length > 0) {
|
|
94
|
+
return { id: claimedInFlight[0].id, inserted: false, attempt_in_flight: true };
|
|
95
|
+
}
|
|
96
|
+
// Step 3: no existing row — insert a new delivery.
|
|
97
|
+
const rows = await adapter.query(new QueryBuilder()
|
|
98
|
+
.from("hazo_notify_channel_deliveries")
|
|
99
|
+
.returning("id"), "POST", {
|
|
100
|
+
inbox_id: i.inbox_id,
|
|
101
|
+
channel_id: i.channel_id,
|
|
102
|
+
payload: JSON.stringify(i.payload),
|
|
103
|
+
flush_after: i.flush_after,
|
|
104
|
+
});
|
|
105
|
+
return { id: rows[0].id, inserted: true, attempt_in_flight: false };
|
|
106
|
+
}
|
|
107
|
+
export async function claimDueDeliveries(channel_id, batch_size) {
|
|
108
|
+
const adapter = getInboxConnection();
|
|
109
|
+
// THE KEY CHANGE (Task 2.6): delegates to adapter.claimRows instead of a
|
|
110
|
+
// dialect-bound raw SQL query (SELECT … FOR UPDATE SKIP LOCKED on Postgres).
|
|
111
|
+
// Strong atomicity on PgAdapter; CAS-optimistic on PostgrestAdapter;
|
|
112
|
+
// serialised transaction on SqliteAdapter. All adapters are supported.
|
|
113
|
+
const claimed = await adapter.claimRows({
|
|
114
|
+
table: "hazo_notify_channel_deliveries",
|
|
115
|
+
where: {
|
|
116
|
+
channel_id,
|
|
117
|
+
status: "pending",
|
|
118
|
+
flush_after: { lte: { $now: true } },
|
|
119
|
+
},
|
|
120
|
+
set: {
|
|
121
|
+
attempt_count: { increment: 1 },
|
|
122
|
+
updated_at: { $now: true },
|
|
123
|
+
},
|
|
124
|
+
returning: ["id", "inbox_id", "channel_id", "payload", "attempt_count"],
|
|
125
|
+
limit: batch_size,
|
|
126
|
+
orderBy: { column: "flush_after", direction: "asc" },
|
|
127
|
+
});
|
|
128
|
+
// Enrich each claimed row with user_id + scope_id from the originating inbox
|
|
129
|
+
// row. N+1 is acceptable: batch sizes are small (~10) and per-call latency
|
|
130
|
+
// dominates. Map `id` → `delivery_id` to match the ClaimedDelivery contract
|
|
131
|
+
// that callers (flush worker) expect.
|
|
132
|
+
return Promise.all(claimed.map(async (r) => {
|
|
133
|
+
const inboxRows = await adapter.query(new QueryBuilder()
|
|
134
|
+
.from("hazo_notify_inbox")
|
|
135
|
+
.select("user_id, scope_id")
|
|
136
|
+
.where("id", "eq", r.inbox_id), "GET");
|
|
137
|
+
const payload = typeof r.payload === "string"
|
|
138
|
+
? JSON.parse(r.payload)
|
|
139
|
+
: r.payload;
|
|
140
|
+
return {
|
|
141
|
+
delivery_id: r.id,
|
|
142
|
+
inbox_id: r.inbox_id,
|
|
143
|
+
channel_id: r.channel_id,
|
|
144
|
+
payload,
|
|
145
|
+
attempt_count: r.attempt_count,
|
|
146
|
+
user_id: inboxRows[0]?.user_id ?? "",
|
|
147
|
+
scope_id: inboxRows[0]?.scope_id ?? "",
|
|
148
|
+
};
|
|
149
|
+
}));
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Task 2.7 — markDeliverySent via CrudService.updateById
|
|
153
|
+
*
|
|
154
|
+
* Sets status=sent, records the provider message_id, and stamps finalized_at.
|
|
155
|
+
* updateById returns T[] — ignored here (fire-and-forget).
|
|
156
|
+
*/
|
|
157
|
+
export async function markDeliverySent(id, message_id) {
|
|
158
|
+
const adapter = getInboxConnection();
|
|
159
|
+
const crud = createCrudService(adapter, "hazo_notify_channel_deliveries");
|
|
160
|
+
await crud.updateById(id, {
|
|
161
|
+
status: "sent",
|
|
162
|
+
message_id,
|
|
163
|
+
finalized_at: new Date().toISOString(),
|
|
164
|
+
updated_at: new Date().toISOString(),
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Task 2.8 — markDeliveryFailed via CrudService.updateById
|
|
169
|
+
*
|
|
170
|
+
* Sets status=failed, stores the error (truncated to 1000 chars), and stamps finalized_at.
|
|
171
|
+
* updateById returns T[] — ignored here (fire-and-forget).
|
|
172
|
+
*/
|
|
173
|
+
export async function markDeliveryFailed(id, error) {
|
|
174
|
+
const adapter = getInboxConnection();
|
|
175
|
+
const crud = createCrudService(adapter, "hazo_notify_channel_deliveries");
|
|
176
|
+
await crud.updateById(id, {
|
|
177
|
+
status: "failed",
|
|
178
|
+
last_error: error.slice(0, 1000),
|
|
179
|
+
finalized_at: new Date().toISOString(),
|
|
180
|
+
updated_at: new Date().toISOString(),
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Task 2.9 — rescheduleDelivery via CrudService.updateById
|
|
185
|
+
*
|
|
186
|
+
* Updates flush_after and last_error for the next retry. Status stays 'pending'.
|
|
187
|
+
* updateById returns T[] — ignored here (fire-and-forget).
|
|
188
|
+
*/
|
|
189
|
+
export async function rescheduleDelivery(id, flush_after, error) {
|
|
190
|
+
const adapter = getInboxConnection();
|
|
191
|
+
const crud = createCrudService(adapter, "hazo_notify_channel_deliveries");
|
|
192
|
+
await crud.updateById(id, {
|
|
193
|
+
flush_after,
|
|
194
|
+
last_error: error,
|
|
195
|
+
updated_at: new Date().toISOString(),
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Task 2.10 — closeStaleBatches via adapter.claimRows
|
|
200
|
+
*
|
|
201
|
+
* Closes inbox rows whose batch_closed_at is empty string (open) and whose
|
|
202
|
+
* created_at is older than (now - window_ms).
|
|
203
|
+
*
|
|
204
|
+
* NULL caveat: SQLite's claimRows WHERE emits `col = ?` which does not match
|
|
205
|
+
* SQL NULL. Empty string '' is used as the sentinel for "open batch" in both
|
|
206
|
+
* SQLite tests and production (see upsertInboxRow). In production (Postgres),
|
|
207
|
+
* rows inserted via this adapter path use '' not NULL, so the '' sentinel is
|
|
208
|
+
* consistent end-to-end.
|
|
209
|
+
*
|
|
210
|
+
* Returns the number of batches closed.
|
|
211
|
+
*/
|
|
212
|
+
export async function closeStaleBatches(window_ms) {
|
|
213
|
+
const adapter = getInboxConnection();
|
|
214
|
+
const cutoff = new Date(Date.now() - window_ms).toISOString();
|
|
215
|
+
const rows = await adapter.claimRows({
|
|
216
|
+
table: "hazo_notify_inbox",
|
|
217
|
+
where: { batch_closed_at: "", created_at: { lte: cutoff } },
|
|
218
|
+
set: { batch_closed_at: { $now: true } },
|
|
219
|
+
returning: ["id"],
|
|
220
|
+
limit: 10000,
|
|
221
|
+
});
|
|
222
|
+
return rows.length;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Task 2.11a — purgeReadInbox via QueryBuilder DELETE
|
|
226
|
+
*
|
|
227
|
+
* Deletes inbox rows that have been read and whose read_at is older than
|
|
228
|
+
* `older_than`. Returns the number of rows deleted.
|
|
229
|
+
*
|
|
230
|
+
* QueryBuilder 'neq' with null → IS NOT NULL (via translateWhereCondition).
|
|
231
|
+
* QueryBuilder 'lt' → read_at < older_than.
|
|
232
|
+
*/
|
|
233
|
+
export async function purgeReadInbox(older_than) {
|
|
234
|
+
const adapter = getInboxConnection();
|
|
235
|
+
const builder = new QueryBuilder()
|
|
236
|
+
.from("hazo_notify_inbox")
|
|
237
|
+
.where("read_at", "neq", null)
|
|
238
|
+
.where("read_at", "lt", older_than);
|
|
239
|
+
const rows = await adapter.query(builder, "DELETE");
|
|
240
|
+
return rows.length;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Task 2.11b — purgeFinalizedDeliveries via QueryBuilder DELETE
|
|
244
|
+
*
|
|
245
|
+
* Deletes delivery rows that have been finalized (sent or failed) and whose
|
|
246
|
+
* finalized_at is older than `older_than`. Returns the number of rows deleted.
|
|
247
|
+
*/
|
|
248
|
+
export async function purgeFinalizedDeliveries(older_than) {
|
|
249
|
+
const adapter = getInboxConnection();
|
|
250
|
+
const builder = new QueryBuilder()
|
|
251
|
+
.from("hazo_notify_channel_deliveries")
|
|
252
|
+
.where("finalized_at", "neq", null)
|
|
253
|
+
.where("finalized_at", "lt", older_than);
|
|
254
|
+
const rows = await adapter.query(builder, "DELETE");
|
|
255
|
+
return rows.length;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Task 2.12a — listUnreadForUser via QueryBuilder
|
|
259
|
+
*
|
|
260
|
+
* Returns unread inbox rows for the given user/scope, ordered by created_at
|
|
261
|
+
* descending with optional cursor (created_at < cursor) and limit.
|
|
262
|
+
*
|
|
263
|
+
* Surface filtering (in_app / banner) requires dialect-specific JSONB or
|
|
264
|
+
* json_extract access. Rather than branching on adapter type at this layer,
|
|
265
|
+
* callers filter in-memory when surface filtering is needed. This keeps the
|
|
266
|
+
* storage layer adapter-agnostic.
|
|
267
|
+
*
|
|
268
|
+
* QueryBuilder 'is' with null → IS NULL (via translateWhereCondition).
|
|
269
|
+
*/
|
|
270
|
+
export async function listUnreadForUser(user_id, scope_id, opts = {}) {
|
|
271
|
+
const adapter = getInboxConnection();
|
|
272
|
+
let builder = new QueryBuilder()
|
|
273
|
+
.from("hazo_notify_inbox")
|
|
274
|
+
.where("user_id", "eq", user_id)
|
|
275
|
+
.where("scope_id", "eq", scope_id)
|
|
276
|
+
.where("read_at", "is", null)
|
|
277
|
+
.order("created_at", "desc")
|
|
278
|
+
.limit(Math.min(opts.limit ?? 20, 100));
|
|
279
|
+
if (opts.cursor) {
|
|
280
|
+
builder = builder.where("created_at", "lt", opts.cursor);
|
|
281
|
+
}
|
|
282
|
+
return await adapter.query(builder, "GET");
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Task 2.12b — unreadCount via QueryBuilder
|
|
286
|
+
*
|
|
287
|
+
* Counts unread inbox rows for the given user/scope.
|
|
288
|
+
* QueryBuilder 'is' with null → IS NULL.
|
|
289
|
+
*/
|
|
290
|
+
export async function unreadCount(user_id, scope_id) {
|
|
291
|
+
const adapter = getInboxConnection();
|
|
292
|
+
const builder = new QueryBuilder()
|
|
293
|
+
.from("hazo_notify_inbox")
|
|
294
|
+
.where("user_id", "eq", user_id)
|
|
295
|
+
.where("scope_id", "eq", scope_id)
|
|
296
|
+
.where("read_at", "is", null);
|
|
297
|
+
const rows = await adapter.query(builder, "GET");
|
|
298
|
+
return rows.length;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Task 2.13a — markRead via claimRows (idempotent)
|
|
302
|
+
*
|
|
303
|
+
* Sets read_at = now() for the given inbox row, scoped by user_id to prevent
|
|
304
|
+
* cross-user reads.
|
|
305
|
+
*
|
|
306
|
+
* Idempotency trade-off: the `read_at: null` condition is omitted from the
|
|
307
|
+
* claimRows WHERE because the clause-builder emits `col = ?` which SQLite's
|
|
308
|
+
* `=` operator does not use to match NULL. Dropping the condition makes the
|
|
309
|
+
* operation idempotent — re-marking an already-read row re-sets read_at = now(),
|
|
310
|
+
* which is harmless for UI-driven calls.
|
|
311
|
+
*/
|
|
312
|
+
export async function markRead(id, user_id) {
|
|
313
|
+
const adapter = getInboxConnection();
|
|
314
|
+
const rows = await adapter.claimRows({
|
|
315
|
+
table: "hazo_notify_inbox",
|
|
316
|
+
where: { id, user_id },
|
|
317
|
+
set: { read_at: { $now: true } },
|
|
318
|
+
returning: ["id"],
|
|
319
|
+
limit: 1,
|
|
320
|
+
});
|
|
321
|
+
return rows.length > 0;
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Task 2.13b — markAllRead via claimRows (idempotent)
|
|
325
|
+
*
|
|
326
|
+
* Sets read_at = now() for every inbox row belonging to the given user/scope.
|
|
327
|
+
*
|
|
328
|
+
* Idempotency trade-off: same as markRead — the `read_at: null` condition is
|
|
329
|
+
* omitted to avoid the SQLite NULL-matching issue. Already-read rows will have
|
|
330
|
+
* their read_at re-stamped, which is acceptable for bulk UI acknowledgment.
|
|
331
|
+
*
|
|
332
|
+
* Returns the count of rows updated (includes any already-read rows).
|
|
333
|
+
*/
|
|
334
|
+
export async function markAllRead(user_id, scope_id) {
|
|
335
|
+
const adapter = getInboxConnection();
|
|
336
|
+
const rows = await adapter.claimRows({
|
|
337
|
+
table: "hazo_notify_inbox",
|
|
338
|
+
where: { user_id, scope_id },
|
|
339
|
+
set: { read_at: { $now: true } },
|
|
340
|
+
returning: ["id"],
|
|
341
|
+
limit: 10000,
|
|
342
|
+
});
|
|
343
|
+
return rows.length;
|
|
344
|
+
}
|
|
345
|
+
//# sourceMappingURL=storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.js","sourceRoot":"","sources":["../../../src/lib/inbox/storage.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
export interface NotificationSurfaces {
|
|
2
|
+
in_app: boolean;
|
|
3
|
+
banner: boolean;
|
|
4
|
+
}
|
|
5
|
+
export interface InboxRow {
|
|
6
|
+
id: string;
|
|
7
|
+
scope_id: string;
|
|
8
|
+
user_id: string;
|
|
9
|
+
event_type: string;
|
|
10
|
+
subject_id: string | null;
|
|
11
|
+
batch_key: string;
|
|
12
|
+
in_app_text: string;
|
|
13
|
+
deep_link: string;
|
|
14
|
+
payload: Record<string, unknown>;
|
|
15
|
+
surfaces: NotificationSurfaces;
|
|
16
|
+
aggregate_count: number;
|
|
17
|
+
batch_closed_at: string | null;
|
|
18
|
+
read_at: string | null;
|
|
19
|
+
created_at: string;
|
|
20
|
+
updated_at: string;
|
|
21
|
+
}
|
|
22
|
+
export interface DeliveryRow {
|
|
23
|
+
id: string;
|
|
24
|
+
inbox_id: string;
|
|
25
|
+
channel_id: string;
|
|
26
|
+
payload: Record<string, unknown>;
|
|
27
|
+
status: "pending" | "sent" | "failed";
|
|
28
|
+
flush_after: string;
|
|
29
|
+
attempt_count: number;
|
|
30
|
+
message_id: string | null;
|
|
31
|
+
last_error: string | null;
|
|
32
|
+
finalized_at: string | null;
|
|
33
|
+
created_at: string;
|
|
34
|
+
updated_at: string;
|
|
35
|
+
}
|
|
36
|
+
export interface DispatchInput {
|
|
37
|
+
event_type: string;
|
|
38
|
+
subject_id: string | null;
|
|
39
|
+
scope_id: string;
|
|
40
|
+
recipient_user_ids: string[];
|
|
41
|
+
in_app_text: string;
|
|
42
|
+
deep_link: string;
|
|
43
|
+
surfaces: NotificationSurfaces;
|
|
44
|
+
channels: Partial<Record<string, boolean>>;
|
|
45
|
+
channel_payloads?: Record<string, Record<string, unknown>>;
|
|
46
|
+
payload?: Record<string, unknown>;
|
|
47
|
+
batch_window_ms?: number;
|
|
48
|
+
}
|
|
49
|
+
export interface DispatchResult {
|
|
50
|
+
inbox_rows_upserted: number;
|
|
51
|
+
inbox_rows_inserted: number;
|
|
52
|
+
inbox_rows_aggregated: number;
|
|
53
|
+
deliveries_created: number;
|
|
54
|
+
deliveries_refreshed: number;
|
|
55
|
+
channels_dispatched: string[];
|
|
56
|
+
}
|
|
57
|
+
export interface UpsertInboxRowInput {
|
|
58
|
+
scope_id: string;
|
|
59
|
+
user_id: string;
|
|
60
|
+
event_type: string;
|
|
61
|
+
subject_id: string | null;
|
|
62
|
+
batch_key: string;
|
|
63
|
+
in_app_text: string;
|
|
64
|
+
deep_link: string;
|
|
65
|
+
payload: Record<string, unknown>;
|
|
66
|
+
surfaces: NotificationSurfaces;
|
|
67
|
+
batch_closed_at: string | null;
|
|
68
|
+
}
|
|
69
|
+
export interface UpsertInboxRowResult {
|
|
70
|
+
id: string;
|
|
71
|
+
inserted: boolean;
|
|
72
|
+
}
|
|
73
|
+
export interface UpsertDeliveryInput {
|
|
74
|
+
inbox_id: string;
|
|
75
|
+
channel_id: string;
|
|
76
|
+
payload: Record<string, unknown>;
|
|
77
|
+
flush_after: string;
|
|
78
|
+
}
|
|
79
|
+
export interface UpsertDeliveryResult {
|
|
80
|
+
id: string;
|
|
81
|
+
inserted: boolean;
|
|
82
|
+
/** True if the worker has already started attempts on this row. */
|
|
83
|
+
attempt_in_flight: boolean;
|
|
84
|
+
}
|
|
85
|
+
export interface ClaimedDelivery {
|
|
86
|
+
delivery_id: string;
|
|
87
|
+
inbox_id: string;
|
|
88
|
+
channel_id: string;
|
|
89
|
+
payload: Record<string, unknown>;
|
|
90
|
+
attempt_count: number;
|
|
91
|
+
user_id: string;
|
|
92
|
+
scope_id: string;
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/lib/inbox/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/lib/inbox/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sweep functions for the inbox worker:
|
|
3
|
+
* - runBatchCloseSweep — closes batches whose window has elapsed
|
|
4
|
+
* - runCleanupSweep — purges read inbox rows + finalized deliveries past retention
|
|
5
|
+
*
|
|
6
|
+
* @packageDocumentation
|
|
7
|
+
*/
|
|
8
|
+
import type { ChannelLogger } from "../../channels/types.js";
|
|
9
|
+
export declare function runBatchCloseSweep(window_ms: number, logger: ChannelLogger): Promise<number>;
|
|
10
|
+
export declare function runCleanupSweep(read_inbox_retention_ms: number, failed_delivery_retention_ms: number, logger: ChannelLogger): Promise<{
|
|
11
|
+
inbox_purged: number;
|
|
12
|
+
deliveries_purged: number;
|
|
13
|
+
}>;
|
|
14
|
+
//# sourceMappingURL=cleanup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cleanup.d.ts","sourceRoot":"","sources":["../../../../src/lib/inbox/worker/cleanup.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAE7D,wBAAsB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CASlG;AAED,wBAAsB,eAAe,CACnC,uBAAuB,EAAE,MAAM,EAC/B,4BAA4B,EAAE,MAAM,EACpC,MAAM,EAAE,aAAa,GACpB,OAAO,CAAC;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,iBAAiB,EAAE,MAAM,CAAA;CAAE,CAAC,CAc9D"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sweep functions for the inbox worker:
|
|
3
|
+
* - runBatchCloseSweep — closes batches whose window has elapsed
|
|
4
|
+
* - runCleanupSweep — purges read inbox rows + finalized deliveries past retention
|
|
5
|
+
*
|
|
6
|
+
* @packageDocumentation
|
|
7
|
+
*/
|
|
8
|
+
import { closeStaleBatches, purgeReadInbox, purgeFinalizedDeliveries, } from "../storage.js";
|
|
9
|
+
export async function runBatchCloseSweep(window_ms, logger) {
|
|
10
|
+
try {
|
|
11
|
+
const n = await closeStaleBatches(window_ms);
|
|
12
|
+
if (n > 0)
|
|
13
|
+
logger.info("inbox.batch_close_sweep", { closed: n });
|
|
14
|
+
return n;
|
|
15
|
+
}
|
|
16
|
+
catch (e) {
|
|
17
|
+
logger.error("inbox.batch_close_sweep_failed", { error: e instanceof Error ? e.message : String(e) });
|
|
18
|
+
return 0;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export async function runCleanupSweep(read_inbox_retention_ms, failed_delivery_retention_ms, logger) {
|
|
22
|
+
try {
|
|
23
|
+
const inbox_cutoff = new Date(Date.now() - read_inbox_retention_ms).toISOString();
|
|
24
|
+
const del_cutoff = new Date(Date.now() - failed_delivery_retention_ms).toISOString();
|
|
25
|
+
const inbox_purged = await purgeReadInbox(inbox_cutoff);
|
|
26
|
+
const deliveries_purged = await purgeFinalizedDeliveries(del_cutoff);
|
|
27
|
+
if (inbox_purged + deliveries_purged > 0) {
|
|
28
|
+
logger.info("inbox.cleanup_sweep", { inbox_purged, deliveries_purged });
|
|
29
|
+
}
|
|
30
|
+
return { inbox_purged, deliveries_purged };
|
|
31
|
+
}
|
|
32
|
+
catch (e) {
|
|
33
|
+
logger.error("inbox.cleanup_sweep_failed", { error: e instanceof Error ? e.message : String(e) });
|
|
34
|
+
return { inbox_purged: 0, deliveries_purged: 0 };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=cleanup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cleanup.js","sourceRoot":"","sources":["../../../../src/lib/inbox/worker/cleanup.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,iBAAiB,EAAE,cAAc,EAAE,wBAAwB,GAC5D,MAAM,eAAe,CAAC;AAGvB,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,SAAiB,EAAE,MAAqB;IAC/E,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,MAAM,iBAAiB,CAAC,SAAS,CAAC,CAAC;QAC7C,IAAI,CAAC,GAAG,CAAC;YAAE,MAAM,CAAC,IAAI,CAAC,yBAAyB,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;QACjE,OAAO,CAAC,CAAC;IACX,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,CAAC,KAAK,CAAC,gCAAgC,EAAE,EAAE,KAAK,EAAE,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACtG,OAAO,CAAC,CAAC;IACX,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,uBAA+B,EAC/B,4BAAoC,EACpC,MAAqB;IAErB,IAAI,CAAC;QACH,MAAM,YAAY,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,uBAAuB,CAAC,CAAC,WAAW,EAAE,CAAC;QAClF,MAAM,UAAU,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,4BAA4B,CAAC,CAAC,WAAW,EAAE,CAAC;QACrF,MAAM,YAAY,GAAG,MAAM,cAAc,CAAC,YAAY,CAAC,CAAC;QACxD,MAAM,iBAAiB,GAAG,MAAM,wBAAwB,CAAC,UAAU,CAAC,CAAC;QACrE,IAAI,YAAY,GAAG,iBAAiB,GAAG,CAAC,EAAE,CAAC;YACzC,MAAM,CAAC,IAAI,CAAC,qBAAqB,EAAE,EAAE,YAAY,EAAE,iBAAiB,EAAE,CAAC,CAAC;QAC1E,CAAC;QACD,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,CAAC;IAC7C,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,CAAC,KAAK,CAAC,4BAA4B,EAAE,EAAE,KAAK,EAAE,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAClG,OAAO,EAAE,YAAY,EAAE,CAAC,EAAE,iBAAiB,EAAE,CAAC,EAAE,CAAC;IACnD,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic per-channel flush worker tick.
|
|
3
|
+
*
|
|
4
|
+
* Replaces the v3 email-only `flushOnce` with `flushChannelOnce(channel_id, opts)`,
|
|
5
|
+
* which works for any registered channel adapter. Retry policy is read from
|
|
6
|
+
* `adapter.capabilities.retry`; `attempt_count` is already incremented at claim
|
|
7
|
+
* (spec §7.2), so the retry condition is `attempt_count < max_attempts`.
|
|
8
|
+
*
|
|
9
|
+
* @packageDocumentation
|
|
10
|
+
*/
|
|
11
|
+
import type { ChannelLogger, ScopeBranding } from "../../channels/types.js";
|
|
12
|
+
export interface FlushOptions {
|
|
13
|
+
batchSize: number;
|
|
14
|
+
concurrency: number;
|
|
15
|
+
resolveRecipient: (user_id: string, scope_id: string, channel_id: string) => Promise<string | null>;
|
|
16
|
+
resolveScopeBranding?: (scope_id: string) => Promise<ScopeBranding | null>;
|
|
17
|
+
logger?: ChannelLogger;
|
|
18
|
+
}
|
|
19
|
+
export interface FlushResult {
|
|
20
|
+
processed: number;
|
|
21
|
+
sent: number;
|
|
22
|
+
failed: number;
|
|
23
|
+
retried: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Process one batch of deliveries due for the given channel.
|
|
27
|
+
* Per-row errors are captured on the row rather than aborting the batch.
|
|
28
|
+
* Concurrency is bounded by `opts.concurrency`.
|
|
29
|
+
*/
|
|
30
|
+
export declare function flushChannelOnce(channel_id: string, opts: FlushOptions): Promise<FlushResult>;
|
|
31
|
+
//# sourceMappingURL=flush.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flush.d.ts","sourceRoot":"","sources":["../../../../src/lib/inbox/worker/flush.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAUH,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAE5E,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACpG,oBAAoB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC;IAC3E,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB;AAED,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAOD;;;;GAIG;AACH,wBAAsB,gBAAgB,CACpC,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,YAAY,GACjB,OAAO,CAAC,WAAW,CAAC,CAyFtB"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic per-channel flush worker tick.
|
|
3
|
+
*
|
|
4
|
+
* Replaces the v3 email-only `flushOnce` with `flushChannelOnce(channel_id, opts)`,
|
|
5
|
+
* which works for any registered channel adapter. Retry policy is read from
|
|
6
|
+
* `adapter.capabilities.retry`; `attempt_count` is already incremented at claim
|
|
7
|
+
* (spec §7.2), so the retry condition is `attempt_count < max_attempts`.
|
|
8
|
+
*
|
|
9
|
+
* @packageDocumentation
|
|
10
|
+
*/
|
|
11
|
+
import { getChannel } from "../../channels/registry.js";
|
|
12
|
+
import { claimDueDeliveries, markDeliverySent, markDeliveryFailed, rescheduleDelivery, } from "../storage.js";
|
|
13
|
+
import { renderForChannel } from "./render.js";
|
|
14
|
+
const noopLogger = {
|
|
15
|
+
info: () => { },
|
|
16
|
+
error: () => { },
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Process one batch of deliveries due for the given channel.
|
|
20
|
+
* Per-row errors are captured on the row rather than aborting the batch.
|
|
21
|
+
* Concurrency is bounded by `opts.concurrency`.
|
|
22
|
+
*/
|
|
23
|
+
export async function flushChannelOnce(channel_id, opts) {
|
|
24
|
+
const adapter = getChannel(channel_id);
|
|
25
|
+
if (!adapter)
|
|
26
|
+
throw new Error(`flushChannelOnce: ${channel_id} not registered`);
|
|
27
|
+
const logger = opts.logger ?? noopLogger;
|
|
28
|
+
const rows = await claimDueDeliveries(channel_id, opts.batchSize);
|
|
29
|
+
if (rows.length === 0)
|
|
30
|
+
return { processed: 0, sent: 0, failed: 0, retried: 0 };
|
|
31
|
+
let sent = 0, failed = 0, retried = 0;
|
|
32
|
+
const runOne = async (row) => {
|
|
33
|
+
try {
|
|
34
|
+
const recipient = await opts.resolveRecipient(row.user_id, row.scope_id, channel_id);
|
|
35
|
+
if (!recipient) {
|
|
36
|
+
await markDeliveryFailed(row.delivery_id, "no_recipient");
|
|
37
|
+
failed++;
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const rendered_bodies = await renderForChannel(adapter, {
|
|
41
|
+
scope_id: row.scope_id, payload: row.payload,
|
|
42
|
+
});
|
|
43
|
+
const branding = opts.resolveScopeBranding
|
|
44
|
+
? await opts.resolveScopeBranding(row.scope_id)
|
|
45
|
+
: null;
|
|
46
|
+
const result = await adapter.send(row.payload, {
|
|
47
|
+
inbox_id: row.inbox_id,
|
|
48
|
+
scope_id: row.scope_id,
|
|
49
|
+
user_id: row.user_id,
|
|
50
|
+
attempt_count: row.attempt_count,
|
|
51
|
+
recipient,
|
|
52
|
+
rendered_bodies,
|
|
53
|
+
branding,
|
|
54
|
+
logger,
|
|
55
|
+
});
|
|
56
|
+
if (result.ok) {
|
|
57
|
+
await markDeliverySent(row.delivery_id, result.message_id ?? "");
|
|
58
|
+
sent++;
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
// attempt_count was already incremented at claim (spec §7.2).
|
|
62
|
+
// First attempt has attempt_count = 1, so `1 < max_attempts` means retry is available.
|
|
63
|
+
if (result.retryable && row.attempt_count < adapter.capabilities.retry.max_attempts) {
|
|
64
|
+
const delay = adapter.capabilities.retry.backoff_ms(row.attempt_count, result.retry_after_ms);
|
|
65
|
+
await rescheduleDelivery(row.delivery_id, new Date(Date.now() + delay).toISOString(), result.error ?? "transient");
|
|
66
|
+
retried++;
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
await markDeliveryFailed(row.delivery_id, result.error ?? "send_failed");
|
|
70
|
+
failed++;
|
|
71
|
+
}
|
|
72
|
+
catch (e) {
|
|
73
|
+
// Uncaught adapter exceptions are conservatively treated as retryable up to the cap (spec §7.2).
|
|
74
|
+
const err = e instanceof Error ? e.message : String(e);
|
|
75
|
+
if (row.attempt_count < adapter.capabilities.retry.max_attempts) {
|
|
76
|
+
const delay = adapter.capabilities.retry.backoff_ms(row.attempt_count);
|
|
77
|
+
await rescheduleDelivery(row.delivery_id, new Date(Date.now() + delay).toISOString(), err);
|
|
78
|
+
retried++;
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
await markDeliveryFailed(row.delivery_id, err);
|
|
82
|
+
failed++;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
// Bounded concurrency: spin up `concurrency` workers that drain the queue.
|
|
87
|
+
const queue = [...rows];
|
|
88
|
+
await Promise.all(Array.from({ length: Math.max(1, opts.concurrency) }, async () => {
|
|
89
|
+
while (queue.length > 0) {
|
|
90
|
+
const next = queue.shift();
|
|
91
|
+
if (next)
|
|
92
|
+
await runOne(next);
|
|
93
|
+
}
|
|
94
|
+
}));
|
|
95
|
+
return { processed: rows.length, sent, failed, retried };
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=flush.js.map
|