@secondlayer/shared 11.12.4 → 11.12.5

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.
@@ -0,0 +1,64 @@
1
+ import { type Kysely, sql } from "kysely";
2
+ import { onControlPlane } from "../src/db/migration-role.ts";
3
+
4
+ /**
5
+ * `webhook_deliveries.webhook_id` has carried no FK since the table was
6
+ * created (0057, as `subscription_deliveries`) — only `outbox_id` got one
7
+ * (0077, `ON DELETE SET NULL`, kept on purpose: delivery history outlives
8
+ * outbox compaction). Deleting a webhook left its delivery history orphaned
9
+ * (1,186 rows had built up in the CI-mirror DB); the dashboard/API never
10
+ * show them, so they just sat there until the 30-day retention sweep.
11
+ *
12
+ * `deliveries_sub_idx (webhook_id, dispatched_at DESC)` already covers the
13
+ * cascade lookup — it predates this migration (0057) and survived the
14
+ * 0126 rename under its old name, so no new index is needed here.
15
+ *
16
+ * `webhook_deliveries` lives on the TARGET (control-plane) DB alongside
17
+ * `webhooks`, like `webhook_outbox` — gate with `onControlPlane`, same as
18
+ * 0139.
19
+ *
20
+ * NOT VALID + a separate VALIDATE keeps the exclusive lock short on a big
21
+ * table: NOT VALID only takes the fast lock to add the constraint (new rows
22
+ * are checked immediately), then VALIDATE scans existing rows without
23
+ * blocking writers the whole time.
24
+ */
25
+ export async function up(db: Kysely<unknown>): Promise<void> {
26
+ await sql`SET lock_timeout = '30s'`.execute(db);
27
+ await onControlPlane(async () => {
28
+ const existing = await sql<{ exists: boolean }>`
29
+ SELECT EXISTS (
30
+ SELECT 1 FROM pg_constraint
31
+ WHERE conname = 'webhook_deliveries_webhook_id_fkey'
32
+ ) AS exists
33
+ `.execute(db);
34
+ if (existing.rows[0]?.exists) return;
35
+
36
+ await sql`
37
+ DELETE FROM webhook_deliveries d
38
+ WHERE NOT EXISTS (SELECT 1 FROM webhooks w WHERE w.id = d.webhook_id)
39
+ `.execute(db);
40
+
41
+ await sql`
42
+ ALTER TABLE webhook_deliveries
43
+ ADD CONSTRAINT webhook_deliveries_webhook_id_fkey
44
+ FOREIGN KEY (webhook_id) REFERENCES webhooks(id) ON DELETE CASCADE
45
+ NOT VALID
46
+ `.execute(db);
47
+
48
+ await sql`
49
+ ALTER TABLE webhook_deliveries
50
+ VALIDATE CONSTRAINT webhook_deliveries_webhook_id_fkey
51
+ `.execute(db);
52
+ });
53
+ }
54
+
55
+ export async function down(db: Kysely<unknown>): Promise<void> {
56
+ await sql`SET lock_timeout = '30s'`.execute(db);
57
+ await onControlPlane(async () => {
58
+ // Deleted orphans are not restored.
59
+ await sql`
60
+ ALTER TABLE webhook_deliveries
61
+ DROP CONSTRAINT IF EXISTS webhook_deliveries_webhook_id_fkey
62
+ `.execute(db);
63
+ });
64
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@secondlayer/shared",
3
- "version": "11.12.4",
3
+ "version": "11.12.5",
4
4
  "description": "Internal building blocks shared across Secondlayer packages.",
5
5
  "license": "MIT",
6
6
  "repository": {