@takosjp/yurucommu-core 3.0.2 → 3.2.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.
@@ -2,7 +2,13 @@
2
2
  * Mobile client tables.
3
3
  */
4
4
 
5
- import { index, sqliteTable, text, uniqueIndex } from "drizzle-orm/sqlite-core";
5
+ import {
6
+ index,
7
+ integer,
8
+ sqliteTable,
9
+ text,
10
+ uniqueIndex,
11
+ } from "drizzle-orm/sqlite-core";
6
12
  import { nowIsoUtc } from "./date-utils.ts";
7
13
  import { actors } from "./actors.ts";
8
14
 
@@ -35,3 +41,97 @@ export const mobilePushRegistrations = sqliteTable(
35
41
  index("mobile_push_registrations_last_seen_idx").on(t.lastSeenAt),
36
42
  ],
37
43
  );
44
+
45
+ /**
46
+ * Product-neutral notification pushers shared by yurucommu-family clients.
47
+ *
48
+ * `pushkey` is an opaque downstream-provider identifier. Provider credentials
49
+ * never live in this table; they stay in the configured stateless gateway.
50
+ */
51
+ export const notificationPushers = sqliteTable(
52
+ "notification_pushers",
53
+ {
54
+ id: text("id").primaryKey(),
55
+ actorApId: text("actor_ap_id")
56
+ .notNull()
57
+ .references(() => actors.apId),
58
+ product: text("product").notNull(),
59
+ scope: text("scope"),
60
+ kind: text("kind").notNull().default("http"),
61
+ appId: text("app_id").notNull(),
62
+ pushkey: text("pushkey").notNull(),
63
+ pushkeyHash: text("pushkey_hash").notNull(),
64
+ appDisplayName: text("app_display_name"),
65
+ deviceDisplayName: text("device_display_name"),
66
+ profileTag: text("profile_tag"),
67
+ lang: text("lang"),
68
+ dataJson: text("data_json").notNull().default("{}"),
69
+ gatewayUrl: text("gateway_url").notNull(),
70
+ createdAt: text("created_at").notNull().$defaultFn(nowIsoUtc),
71
+ updatedAt: text("updated_at")
72
+ .notNull()
73
+ .$defaultFn(nowIsoUtc)
74
+ .$onUpdateFn(nowIsoUtc),
75
+ lastSeenAt: text("last_seen_at").notNull().$defaultFn(nowIsoUtc),
76
+ },
77
+ (t) => [
78
+ uniqueIndex("notification_pushers_actor_product_app_pushkey_idx").on(
79
+ t.actorApId,
80
+ t.product,
81
+ t.appId,
82
+ t.pushkeyHash,
83
+ ),
84
+ index("notification_pushers_actor_product_idx").on(t.actorApId, t.product),
85
+ uniqueIndex("notification_pushers_device_idx").on(
86
+ t.product,
87
+ t.appId,
88
+ t.pushkeyHash,
89
+ ),
90
+ index("notification_pushers_last_seen_idx").on(t.lastSeenAt),
91
+ ],
92
+ );
93
+
94
+ /**
95
+ * Durable outbox for push delivery. A migration-owned trigger writes one row
96
+ * whenever an unread inbox entry is created. Queue messages contain only `id`.
97
+ */
98
+ export const notificationPushJobs = sqliteTable(
99
+ "notification_push_jobs",
100
+ {
101
+ id: text("id").primaryKey(),
102
+ actorApId: text("actor_ap_id").notNull(),
103
+ activityApId: text("activity_ap_id").notNull(),
104
+ // Explicit for notification sources that do not create an inbox row (for
105
+ // example community talk). NULL means infer social/direct from the object.
106
+ product: text("product"),
107
+ status: text("status").notNull().default("pending"),
108
+ // Per-claim fencing token. Every processing-state mutation must match this
109
+ // value so an expired worker cannot overwrite a reclaimed job.
110
+ processingToken: text("processing_token"),
111
+ attempts: integer("attempts").notNull().default(0),
112
+ pendingPusherIdsJson: text("pending_pusher_ids_json"),
113
+ nextAttemptAt: text("next_attempt_at").notNull().$defaultFn(nowIsoUtc),
114
+ lastError: text("last_error"),
115
+ createdAt: text("created_at").notNull().$defaultFn(nowIsoUtc),
116
+ updatedAt: text("updated_at")
117
+ .notNull()
118
+ .$defaultFn(nowIsoUtc)
119
+ .$onUpdateFn(nowIsoUtc),
120
+ deliveredAt: text("delivered_at"),
121
+ },
122
+ (t) => [
123
+ uniqueIndex("notification_push_jobs_actor_activity_idx").on(
124
+ t.actorApId,
125
+ t.activityApId,
126
+ ),
127
+ index("notification_push_jobs_status_next_idx").on(
128
+ t.status,
129
+ t.nextAttemptAt,
130
+ ),
131
+ index("notification_push_jobs_terminal_retention_idx").on(
132
+ t.status,
133
+ t.updatedAt,
134
+ ),
135
+ index("notification_push_jobs_actor_idx").on(t.actorApId),
136
+ ],
137
+ );