@hogsend/db 0.25.0 → 0.27.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.
@@ -197,6 +197,13 @@
197
197
  "when": 1781546768414,
198
198
  "tag": "0027_wealthy_aqueduct",
199
199
  "breakpoints": true
200
+ },
201
+ {
202
+ "idx": 28,
203
+ "version": "7",
204
+ "when": 1782060240750,
205
+ "tag": "0028_eminent_war_machine",
206
+ "breakpoints": true
200
207
  }
201
208
  ]
202
209
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hogsend/db",
3
- "version": "0.25.0",
3
+ "version": "0.27.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -18,6 +18,7 @@ export * from "./journey-configs.js";
18
18
  export * from "./journey-logs.js";
19
19
  export * from "./journey-states.js";
20
20
  export * from "./link-clicks.js";
21
+ export * from "./links.js";
21
22
  export * from "./provider-credentials.js";
22
23
  export * from "./relations.js";
23
24
  export * from "./tracked-links.js";
@@ -0,0 +1,44 @@
1
+ import { index, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
2
+ import { timestamps } from "./_shared.js";
3
+
4
+ /**
5
+ * Operator-owned / standalone tracked links — the managed surface behind the
6
+ * Studio "Links" view and any non-email channel (Discord, SMS, share links). A
7
+ * `links` row is the durable, named identity of a tracked link; the click
8
+ * counter + per-hit `link_clicks` live in `tracked_links` (which points back
9
+ * here via `link_id`). Email's own per-send rewritten links do NOT create a
10
+ * `links` row (they keep `tracked_links.link_id` NULL) — email stays a separate
11
+ * consumer of the same click spine.
12
+ */
13
+ export const links = pgTable(
14
+ "links",
15
+ {
16
+ id: uuid("id").defaultRandom().primaryKey(),
17
+ originalUrl: text("original_url").notNull(),
18
+ // "personal" = single-recipient, identity-bearing (carries `distinctId`, may
19
+ // mint a SINGLE-USE `hs_t`); "public" = shareable, NEVER carries a person
20
+ // token (campaign/UTM attribution only). Default "public" — the safe default.
21
+ type: text("type").notNull().default("public"),
22
+ // Operator-facing name (Studio list).
23
+ label: text("label"),
24
+ // UTM-style campaign grouping for public links.
25
+ campaign: text("campaign"),
26
+ // Originating channel: "studio" | "discord" | "sms" | … (open string).
27
+ source: text("source").notNull(),
28
+ // The canonical contact key a click should stitch the visitor's anon session
29
+ // into — set ONLY for personal links; NULL for public/broadcast (a shareable
30
+ // link must never carry a person).
31
+ distinctId: text("distinct_id"),
32
+ // The admin actor who minted it (mirrors api_keys.createdBy).
33
+ createdBy: text("created_by"),
34
+ // Soft-delete: archive (not hard-delete) so historical `link_clicks` survive
35
+ // (the `tracked_links.link_id` FK is ON DELETE set null as a backstop).
36
+ archivedAt: timestamp("archived_at", { withTimezone: true }),
37
+ ...timestamps,
38
+ },
39
+ (table) => [
40
+ index("links_source_idx").on(table.source),
41
+ index("links_campaign_idx").on(table.campaign),
42
+ index("links_created_at_idx").on(table.createdAt),
43
+ ],
44
+ );
@@ -9,6 +9,7 @@ import {
9
9
  } from "drizzle-orm/pg-core";
10
10
  import { timestamps } from "./_shared.js";
11
11
  import { emailSends } from "./email-sends.js";
12
+ import { links } from "./links.js";
12
13
 
13
14
  export const trackedLinks = pgTable(
14
15
  "tracked_links",
@@ -21,6 +22,13 @@ export const trackedLinks = pgTable(
21
22
  emailSendId: uuid("email_send_id").references(() => emailSends.id, {
22
23
  onDelete: "cascade",
23
24
  }),
25
+ // The managed `links` row this click-counter belongs to, when the link was
26
+ // minted via `mintLink` (Studio / Discord / share links). NULL for email's
27
+ // per-send rewritten links (they resolve identity from `email_sends`). ON
28
+ // DELETE set null so archiving/removing a `links` row keeps the click spine.
29
+ linkId: uuid("link_id").references(() => links.id, {
30
+ onDelete: "set null",
31
+ }),
24
32
  // Subject of a stitch-bearing NON-email link: the canonical contact key the
25
33
  // click should fold the visitor's anon session into. NULL for broadcast
26
34
  // links (Discord/referral default) — broadcast links are tracked for click
@@ -46,5 +54,8 @@ export const trackedLinks = pgTable(
46
54
  }),
47
55
  ...timestamps,
48
56
  },
49
- (table) => [index("tracked_links_email_send_id_idx").on(table.emailSendId)],
57
+ (table) => [
58
+ index("tracked_links_email_send_id_idx").on(table.emailSendId),
59
+ index("tracked_links_link_id_idx").on(table.linkId),
60
+ ],
50
61
  );