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,120 @@
|
|
|
1
|
+
// src/lib/inbox/dialects/sqlite.ts
|
|
2
|
+
// SQLite uses ? params (not $1), no jsonb cast, app-supplied UUIDs,
|
|
3
|
+
// and SELECT ... RETURNING (3.35+).
|
|
4
|
+
//
|
|
5
|
+
// Concurrent claim: SQLite is single-writer; a BEGIN IMMEDIATE transaction
|
|
6
|
+
// serialises the claim+update step. SKIP LOCKED is not needed.
|
|
7
|
+
//
|
|
8
|
+
// Insert-vs-update detection:
|
|
9
|
+
// `rowid = last_insert_rowid()` is unreliable for ON CONFLICT DO UPDATE —
|
|
10
|
+
// SQLite sets last_insert_rowid() to the existing row's rowid on conflict,
|
|
11
|
+
// so the expression is always 1 (true). Instead:
|
|
12
|
+
// - inbox rows: check `aggregate_count = 1` (1 means freshly inserted)
|
|
13
|
+
// - deliveries: check `id = ?` with the candidate new_id (true on fresh insert)
|
|
14
|
+
//
|
|
15
|
+
// Datetime format:
|
|
16
|
+
// The dispatcher stores flush_after as a JS ISO 8601 string ("2026-05-16T13:00:00.000Z").
|
|
17
|
+
// SQLite's datetime('now') returns "2026-05-16 13:00:00" (space-separated, no ms/Z).
|
|
18
|
+
// Comparing them lexicographically always treats ISO as "greater", so `<= datetime('now')`
|
|
19
|
+
// is never true. Use strftime('%Y-%m-%dT%H:%M:%SZ','now') to match ISO format.
|
|
20
|
+
import { randomUUID } from "crypto";
|
|
21
|
+
export const SqliteDialect = {
|
|
22
|
+
async upsertInboxRow(conn, i) {
|
|
23
|
+
// Try INSERT; on conflict UPDATE; detect which path ran via aggregate_count.
|
|
24
|
+
// aggregate_count = 1 means freshly inserted (default value); > 1 means aggregated.
|
|
25
|
+
// (rowid = last_insert_rowid() is unreliable for ON CONFLICT DO UPDATE — see file header.)
|
|
26
|
+
const new_id = randomUUID();
|
|
27
|
+
const rows = await conn.raw(`INSERT INTO hazo_notify_inbox (
|
|
28
|
+
id, scope_id, user_id, event_type, subject_id, batch_key,
|
|
29
|
+
in_app_text, deep_link, payload, surfaces, batch_closed_at
|
|
30
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
31
|
+
ON CONFLICT (batch_key) WHERE batch_closed_at IS NULL DO UPDATE SET
|
|
32
|
+
aggregate_count = aggregate_count + 1,
|
|
33
|
+
in_app_text = excluded.in_app_text,
|
|
34
|
+
payload = excluded.payload,
|
|
35
|
+
updated_at = datetime('now')
|
|
36
|
+
RETURNING id, aggregate_count`, [
|
|
37
|
+
new_id, i.scope_id, i.user_id, i.event_type, i.subject_id, i.batch_key,
|
|
38
|
+
i.in_app_text, i.deep_link,
|
|
39
|
+
JSON.stringify(i.payload), JSON.stringify(i.surfaces),
|
|
40
|
+
i.batch_closed_at,
|
|
41
|
+
]);
|
|
42
|
+
return { id: rows[0].id, inserted: rows[0].aggregate_count === 1 };
|
|
43
|
+
},
|
|
44
|
+
async upsertDelivery(conn, i) {
|
|
45
|
+
// Detect insert-vs-update by comparing the returned id against the candidate new_id.
|
|
46
|
+
// On a fresh INSERT the returned id == new_id; on conflict the existing id is returned.
|
|
47
|
+
// (rowid = last_insert_rowid() is unreliable for ON CONFLICT DO UPDATE — see file header.)
|
|
48
|
+
const new_id = randomUUID();
|
|
49
|
+
const rows = await conn.raw(`INSERT INTO hazo_notify_channel_deliveries (
|
|
50
|
+
id, inbox_id, channel_id, payload, flush_after
|
|
51
|
+
) VALUES (?, ?, ?, ?, ?)
|
|
52
|
+
ON CONFLICT (inbox_id, channel_id) DO UPDATE SET
|
|
53
|
+
payload = excluded.payload,
|
|
54
|
+
flush_after = CASE WHEN attempt_count = 0 THEN excluded.flush_after ELSE flush_after END,
|
|
55
|
+
updated_at = datetime('now')
|
|
56
|
+
RETURNING id, attempt_count`, [new_id, i.inbox_id, i.channel_id, JSON.stringify(i.payload), i.flush_after]);
|
|
57
|
+
return {
|
|
58
|
+
id: rows[0].id,
|
|
59
|
+
inserted: rows[0].id === new_id,
|
|
60
|
+
attempt_in_flight: rows[0].attempt_count > 0,
|
|
61
|
+
};
|
|
62
|
+
},
|
|
63
|
+
async claimDueDeliveries(conn, channel_id, batch_size) {
|
|
64
|
+
// Two-step under BEGIN IMMEDIATE: select candidate ids, then UPDATE+RETURNING.
|
|
65
|
+
//
|
|
66
|
+
// flush_after is stored as ISO 8601 ("2026-05-16T13:00:00.000Z").
|
|
67
|
+
// SQLite's datetime('now') returns "2026-05-16 13:00:00" (space-separated, no Z).
|
|
68
|
+
// These are lexicographically incompatible, so we use strftime('%Y-%m-%dT%H:%M:%SZ','now')
|
|
69
|
+
// to match the ISO format stored in flush_after. See file header for full explanation.
|
|
70
|
+
return await conn.raw(`WITH claimed AS (
|
|
71
|
+
SELECT id FROM hazo_notify_channel_deliveries
|
|
72
|
+
WHERE channel_id = ? AND status = 'pending'
|
|
73
|
+
AND flush_after <= strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
|
|
74
|
+
ORDER BY flush_after ASC LIMIT ?
|
|
75
|
+
)
|
|
76
|
+
UPDATE hazo_notify_channel_deliveries
|
|
77
|
+
SET attempt_count = attempt_count + 1, updated_at = datetime('now')
|
|
78
|
+
WHERE id IN (SELECT id FROM claimed)
|
|
79
|
+
RETURNING
|
|
80
|
+
id AS delivery_id, inbox_id, channel_id, payload, attempt_count,
|
|
81
|
+
(SELECT user_id FROM hazo_notify_inbox WHERE id = inbox_id) AS user_id,
|
|
82
|
+
(SELECT scope_id FROM hazo_notify_inbox WHERE id = inbox_id) AS scope_id`, [channel_id, batch_size]);
|
|
83
|
+
},
|
|
84
|
+
async markDeliverySent(conn, id, message_id) {
|
|
85
|
+
await conn.raw(`UPDATE hazo_notify_channel_deliveries
|
|
86
|
+
SET status='sent', message_id=?, finalized_at=datetime('now'), updated_at=datetime('now')
|
|
87
|
+
WHERE id=?`, [message_id, id]);
|
|
88
|
+
},
|
|
89
|
+
async markDeliveryFailed(conn, id, error) {
|
|
90
|
+
await conn.raw(`UPDATE hazo_notify_channel_deliveries
|
|
91
|
+
SET status='failed', last_error=?, finalized_at=datetime('now'), updated_at=datetime('now')
|
|
92
|
+
WHERE id=?`, [error.slice(0, 1000), id]);
|
|
93
|
+
},
|
|
94
|
+
async rescheduleDelivery(conn, id, flush_after, error) {
|
|
95
|
+
await conn.raw(`UPDATE hazo_notify_channel_deliveries
|
|
96
|
+
SET flush_after=?, last_error=?, updated_at=datetime('now')
|
|
97
|
+
WHERE id=?`, [flush_after, error.slice(0, 1000), id]);
|
|
98
|
+
},
|
|
99
|
+
async closeStaleBatches(conn, window_ms) {
|
|
100
|
+
const rows = await conn.raw(`UPDATE hazo_notify_inbox
|
|
101
|
+
SET batch_closed_at = datetime('now')
|
|
102
|
+
WHERE batch_closed_at IS NULL
|
|
103
|
+
AND datetime(created_at, '+' || ? || ' seconds') < datetime('now')
|
|
104
|
+
RETURNING id`, [Math.floor(window_ms / 1000)]);
|
|
105
|
+
return rows.length;
|
|
106
|
+
},
|
|
107
|
+
async purgeReadInbox(conn, older_than) {
|
|
108
|
+
const rows = await conn.raw(`DELETE FROM hazo_notify_inbox
|
|
109
|
+
WHERE read_at IS NOT NULL AND read_at < ?
|
|
110
|
+
RETURNING id`, [older_than]);
|
|
111
|
+
return rows.length;
|
|
112
|
+
},
|
|
113
|
+
async purgeFinalizedDeliveries(conn, older_than) {
|
|
114
|
+
const rows = await conn.raw(`DELETE FROM hazo_notify_channel_deliveries
|
|
115
|
+
WHERE finalized_at IS NOT NULL AND finalized_at < ?
|
|
116
|
+
RETURNING id`, [older_than]);
|
|
117
|
+
return rows.length;
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
//# sourceMappingURL=sqlite.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite.js","sourceRoot":"","sources":["../../../../src/lib/inbox/dialects/sqlite.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { HazoConnectInstance } from "../../template_manager/types.js";
|
|
2
|
+
export interface UpsertInboxRowInput {
|
|
3
|
+
scope_id: string;
|
|
4
|
+
user_id: string;
|
|
5
|
+
event_type: string;
|
|
6
|
+
subject_id: string | null;
|
|
7
|
+
batch_key: string;
|
|
8
|
+
in_app_text: string;
|
|
9
|
+
deep_link: string;
|
|
10
|
+
payload: Record<string, unknown>;
|
|
11
|
+
surfaces: {
|
|
12
|
+
in_app: boolean;
|
|
13
|
+
banner: boolean;
|
|
14
|
+
};
|
|
15
|
+
batch_closed_at: string | null;
|
|
16
|
+
}
|
|
17
|
+
export interface UpsertInboxRowResult {
|
|
18
|
+
id: string;
|
|
19
|
+
inserted: boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface UpsertDeliveryInput {
|
|
22
|
+
inbox_id: string;
|
|
23
|
+
channel_id: string;
|
|
24
|
+
payload: Record<string, unknown>;
|
|
25
|
+
flush_after: string;
|
|
26
|
+
}
|
|
27
|
+
export interface UpsertDeliveryResult {
|
|
28
|
+
id: string;
|
|
29
|
+
inserted: boolean;
|
|
30
|
+
/** True if the worker has already started attempts on this row. */
|
|
31
|
+
attempt_in_flight: boolean;
|
|
32
|
+
}
|
|
33
|
+
export interface ClaimedDelivery {
|
|
34
|
+
delivery_id: string;
|
|
35
|
+
inbox_id: string;
|
|
36
|
+
channel_id: string;
|
|
37
|
+
payload: Record<string, unknown>;
|
|
38
|
+
attempt_count: number;
|
|
39
|
+
user_id: string;
|
|
40
|
+
scope_id: string;
|
|
41
|
+
}
|
|
42
|
+
export interface InboxDialect {
|
|
43
|
+
upsertInboxRow(conn: HazoConnectInstance, input: UpsertInboxRowInput): Promise<UpsertInboxRowResult>;
|
|
44
|
+
upsertDelivery(conn: HazoConnectInstance, input: UpsertDeliveryInput): Promise<UpsertDeliveryResult>;
|
|
45
|
+
claimDueDeliveries(conn: HazoConnectInstance, channel_id: string, batch_size: number): Promise<ClaimedDelivery[]>;
|
|
46
|
+
markDeliverySent(conn: HazoConnectInstance, delivery_id: string, message_id: string): Promise<void>;
|
|
47
|
+
markDeliveryFailed(conn: HazoConnectInstance, delivery_id: string, error: string): Promise<void>;
|
|
48
|
+
rescheduleDelivery(conn: HazoConnectInstance, delivery_id: string, flush_after: string, error: string): Promise<void>;
|
|
49
|
+
closeStaleBatches(conn: HazoConnectInstance, window_ms: number): Promise<number>;
|
|
50
|
+
purgeReadInbox(conn: HazoConnectInstance, older_than: string): Promise<number>;
|
|
51
|
+
purgeFinalizedDeliveries(conn: HazoConnectInstance, older_than: string): Promise<number>;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/lib/inbox/dialects/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAE3E,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,QAAQ,EAAE;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,CAAC;IAC/C,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,OAAO,CAAC;IAClB,mEAAmE;IACnE,iBAAiB,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,cAAc,CAAC,IAAI,EAAE,mBAAmB,EAAE,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACrG,cAAc,CAAC,IAAI,EAAE,mBAAmB,EAAE,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACrG,kBAAkB,CAAC,IAAI,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;IAClH,gBAAgB,CAAC,IAAI,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpG,kBAAkB,CAAC,IAAI,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjG,kBAAkB,CAAC,IAAI,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtH,iBAAiB,CAAC,IAAI,EAAE,mBAAmB,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACjF,cAAc,CAAC,IAAI,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/E,wBAAwB,CAAC,IAAI,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC1F"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/lib/inbox/dialects/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { initInbox } from "./connection.js";
|
|
2
|
+
export type { InitInboxOptions } from "./connection.js";
|
|
3
|
+
export type { InboxRow, DeliveryRow, NotificationSurfaces, DispatchInput, DispatchResult, } from "./types.js";
|
|
4
|
+
export { listUnreadForUser, unreadCount, markRead, markAllRead, } from "./storage.js";
|
|
5
|
+
export { startInboxWorker } from "./worker/start.js";
|
|
6
|
+
export type { StartInboxWorkerOptions, WorkerHandle } from "./worker/start.js";
|
|
7
|
+
export { flushChannelOnce } from "./worker/flush.js";
|
|
8
|
+
export type { FlushOptions, FlushResult } from "./worker/flush.js";
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/inbox/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,YAAY,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,YAAY,EACV,QAAQ,EAAE,WAAW,EAAE,oBAAoB,EAC3C,aAAa,EAAE,cAAc,GAC9B,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,iBAAiB,EAAE,WAAW,EAAE,QAAQ,EAAE,WAAW,GACtD,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,YAAY,EAAE,uBAAuB,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC/E,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// src/lib/inbox/index.ts — replacement
|
|
2
|
+
export { initInbox } from "./connection.js";
|
|
3
|
+
export { listUnreadForUser, unreadCount, markRead, markAllRead, } from "./storage.js";
|
|
4
|
+
export { startInboxWorker } from "./worker/start.js";
|
|
5
|
+
export { flushChannelOnce } from "./worker/flush.js";
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/inbox/index.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAM5C,OAAO,EACL,iBAAiB,EAAE,WAAW,EAAE,QAAQ,EAAE,WAAW,GACtD,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAErD,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import type { UpsertInboxRowInput, UpsertInboxRowResult, UpsertDeliveryInput, UpsertDeliveryResult, ClaimedDelivery, InboxRow } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Upsert an inbox row using a claim-first strategy:
|
|
4
|
+
*
|
|
5
|
+
* 1. Try to increment an existing open batch that matches `batch_key` and
|
|
6
|
+
* `batch_closed_at`. This preserves the increment-on-conflict semantics of
|
|
7
|
+
* the original Postgres `ON CONFLICT (batch_key) WHERE batch_closed_at IS NULL`
|
|
8
|
+
* partial-index upsert.
|
|
9
|
+
* 2. If no open batch found, insert a fresh row.
|
|
10
|
+
*
|
|
11
|
+
* Race-condition trade-off: if two workers concurrently find no open batch and
|
|
12
|
+
* both insert, both succeed (no unique index guards the SQLite/adapter path).
|
|
13
|
+
* The original Postgres SQL used a partial unique index for true atomicity.
|
|
14
|
+
* For Hazodocs (single-worker), this race window is acceptable; adding retry
|
|
15
|
+
* logic would require error-code parsing not available in hazo_connect.
|
|
16
|
+
*
|
|
17
|
+
* SQLite NULL caveat: the clause-builder emits `col = ?` which SQLite's `=`
|
|
18
|
+
* operator does not use to match NULL (needs IS NULL). In production (Postgres),
|
|
19
|
+
* the behaviour is correct because Postgres's `=` also doesn't match NULL — the
|
|
20
|
+
* partial index `WHERE batch_closed_at IS NULL` handles the constraint there.
|
|
21
|
+
* In SQLite-backed tests use empty string instead of null for batch_closed_at.
|
|
22
|
+
*/
|
|
23
|
+
export declare function upsertInboxRow(i: UpsertInboxRowInput): Promise<UpsertInboxRowResult>;
|
|
24
|
+
/**
|
|
25
|
+
* Upsert a channel delivery row using a two-claim strategy that preserves the
|
|
26
|
+
* original Postgres CASE-WHEN semantic for `flush_after`:
|
|
27
|
+
*
|
|
28
|
+
* 1. Claim an **existing fresh row** (attempt_count = 0) — update both `payload`
|
|
29
|
+
* and `flush_after` (the delivery hasn't been tried yet, so rescheduling is safe).
|
|
30
|
+
* 2. Claim an **existing in-flight row** (attempt_count > 0) — update `payload`
|
|
31
|
+
* only; preserve `flush_after` to avoid resetting an active retry schedule.
|
|
32
|
+
* 3. If neither claim succeeds, no row exists — insert a new one.
|
|
33
|
+
*
|
|
34
|
+
* The two claims are mutually exclusive so at most one succeeds per call.
|
|
35
|
+
*/
|
|
36
|
+
export declare function upsertDelivery(i: UpsertDeliveryInput): Promise<UpsertDeliveryResult>;
|
|
37
|
+
export declare function claimDueDeliveries(channel_id: string, batch_size: number): Promise<ClaimedDelivery[]>;
|
|
38
|
+
/**
|
|
39
|
+
* Task 2.7 — markDeliverySent via CrudService.updateById
|
|
40
|
+
*
|
|
41
|
+
* Sets status=sent, records the provider message_id, and stamps finalized_at.
|
|
42
|
+
* updateById returns T[] — ignored here (fire-and-forget).
|
|
43
|
+
*/
|
|
44
|
+
export declare function markDeliverySent(id: string, message_id: string): Promise<void>;
|
|
45
|
+
/**
|
|
46
|
+
* Task 2.8 — markDeliveryFailed via CrudService.updateById
|
|
47
|
+
*
|
|
48
|
+
* Sets status=failed, stores the error (truncated to 1000 chars), and stamps finalized_at.
|
|
49
|
+
* updateById returns T[] — ignored here (fire-and-forget).
|
|
50
|
+
*/
|
|
51
|
+
export declare function markDeliveryFailed(id: string, error: string): Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* Task 2.9 — rescheduleDelivery via CrudService.updateById
|
|
54
|
+
*
|
|
55
|
+
* Updates flush_after and last_error for the next retry. Status stays 'pending'.
|
|
56
|
+
* updateById returns T[] — ignored here (fire-and-forget).
|
|
57
|
+
*/
|
|
58
|
+
export declare function rescheduleDelivery(id: string, flush_after: string, error: string): Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* Task 2.10 — closeStaleBatches via adapter.claimRows
|
|
61
|
+
*
|
|
62
|
+
* Closes inbox rows whose batch_closed_at is empty string (open) and whose
|
|
63
|
+
* created_at is older than (now - window_ms).
|
|
64
|
+
*
|
|
65
|
+
* NULL caveat: SQLite's claimRows WHERE emits `col = ?` which does not match
|
|
66
|
+
* SQL NULL. Empty string '' is used as the sentinel for "open batch" in both
|
|
67
|
+
* SQLite tests and production (see upsertInboxRow). In production (Postgres),
|
|
68
|
+
* rows inserted via this adapter path use '' not NULL, so the '' sentinel is
|
|
69
|
+
* consistent end-to-end.
|
|
70
|
+
*
|
|
71
|
+
* Returns the number of batches closed.
|
|
72
|
+
*/
|
|
73
|
+
export declare function closeStaleBatches(window_ms: number): Promise<number>;
|
|
74
|
+
/**
|
|
75
|
+
* Task 2.11a — purgeReadInbox via QueryBuilder DELETE
|
|
76
|
+
*
|
|
77
|
+
* Deletes inbox rows that have been read and whose read_at is older than
|
|
78
|
+
* `older_than`. Returns the number of rows deleted.
|
|
79
|
+
*
|
|
80
|
+
* QueryBuilder 'neq' with null → IS NOT NULL (via translateWhereCondition).
|
|
81
|
+
* QueryBuilder 'lt' → read_at < older_than.
|
|
82
|
+
*/
|
|
83
|
+
export declare function purgeReadInbox(older_than: string): Promise<number>;
|
|
84
|
+
/**
|
|
85
|
+
* Task 2.11b — purgeFinalizedDeliveries via QueryBuilder DELETE
|
|
86
|
+
*
|
|
87
|
+
* Deletes delivery rows that have been finalized (sent or failed) and whose
|
|
88
|
+
* finalized_at is older than `older_than`. Returns the number of rows deleted.
|
|
89
|
+
*/
|
|
90
|
+
export declare function purgeFinalizedDeliveries(older_than: string): Promise<number>;
|
|
91
|
+
export interface ListOptions {
|
|
92
|
+
limit?: number;
|
|
93
|
+
cursor?: string;
|
|
94
|
+
surface?: "in_app" | "banner";
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Task 2.12a — listUnreadForUser via QueryBuilder
|
|
98
|
+
*
|
|
99
|
+
* Returns unread inbox rows for the given user/scope, ordered by created_at
|
|
100
|
+
* descending with optional cursor (created_at < cursor) and limit.
|
|
101
|
+
*
|
|
102
|
+
* Surface filtering (in_app / banner) requires dialect-specific JSONB or
|
|
103
|
+
* json_extract access. Rather than branching on adapter type at this layer,
|
|
104
|
+
* callers filter in-memory when surface filtering is needed. This keeps the
|
|
105
|
+
* storage layer adapter-agnostic.
|
|
106
|
+
*
|
|
107
|
+
* QueryBuilder 'is' with null → IS NULL (via translateWhereCondition).
|
|
108
|
+
*/
|
|
109
|
+
export declare function listUnreadForUser(user_id: string, scope_id: string, opts?: ListOptions): Promise<InboxRow[]>;
|
|
110
|
+
/**
|
|
111
|
+
* Task 2.12b — unreadCount via QueryBuilder
|
|
112
|
+
*
|
|
113
|
+
* Counts unread inbox rows for the given user/scope.
|
|
114
|
+
* QueryBuilder 'is' with null → IS NULL.
|
|
115
|
+
*/
|
|
116
|
+
export declare function unreadCount(user_id: string, scope_id: string): Promise<number>;
|
|
117
|
+
/**
|
|
118
|
+
* Task 2.13a — markRead via claimRows (idempotent)
|
|
119
|
+
*
|
|
120
|
+
* Sets read_at = now() for the given inbox row, scoped by user_id to prevent
|
|
121
|
+
* cross-user reads.
|
|
122
|
+
*
|
|
123
|
+
* Idempotency trade-off: the `read_at: null` condition is omitted from the
|
|
124
|
+
* claimRows WHERE because the clause-builder emits `col = ?` which SQLite's
|
|
125
|
+
* `=` operator does not use to match NULL. Dropping the condition makes the
|
|
126
|
+
* operation idempotent — re-marking an already-read row re-sets read_at = now(),
|
|
127
|
+
* which is harmless for UI-driven calls.
|
|
128
|
+
*/
|
|
129
|
+
export declare function markRead(id: string, user_id: string): Promise<boolean>;
|
|
130
|
+
/**
|
|
131
|
+
* Task 2.13b — markAllRead via claimRows (idempotent)
|
|
132
|
+
*
|
|
133
|
+
* Sets read_at = now() for every inbox row belonging to the given user/scope.
|
|
134
|
+
*
|
|
135
|
+
* Idempotency trade-off: same as markRead — the `read_at: null` condition is
|
|
136
|
+
* omitted to avoid the SQLite NULL-matching issue. Already-read rows will have
|
|
137
|
+
* their read_at re-stamped, which is acceptable for bulk UI acknowledgment.
|
|
138
|
+
*
|
|
139
|
+
* Returns the count of rows updated (includes any already-read rows).
|
|
140
|
+
*/
|
|
141
|
+
export declare function markAllRead(user_id: string, scope_id: string): Promise<number>;
|
|
142
|
+
//# sourceMappingURL=storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../../src/lib/inbox/storage.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,EACnB,oBAAoB,EACpB,eAAe,EACf,QAAQ,EACT,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,cAAc,CAAC,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CA0C1F;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,cAAc,CAAC,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CA2C1F;AAED,wBAAsB,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,CAwD3G;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CAAC,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CASpF;AAED;;;;;GAKG;AACH,wBAAsB,kBAAkB,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CASjF;AAED;;;;;GAKG;AACH,wBAAsB,kBAAkB,CAAC,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQtG;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAW1E;AAED;;;;;;;;GAQG;AACH,wBAAsB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQxE;AAED;;;;;GAKG;AACH,wBAAsB,wBAAwB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQlF;AAID,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;CAC/B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,WAAgB,GACrB,OAAO,CAAC,QAAQ,EAAE,CAAC,CAarB;AAED;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CASpF;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAU5E;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAUpF"}
|