synomem 0.9.6 → 0.10.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.
Files changed (118) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +5 -3
  3. package/dist/actor-directory.d.ts +34 -0
  4. package/dist/actor-directory.d.ts.map +1 -0
  5. package/dist/actor-directory.js +52 -0
  6. package/dist/actor-directory.js.map +1 -0
  7. package/dist/backend.d.ts +3 -0
  8. package/dist/backend.d.ts.map +1 -1
  9. package/dist/backend.js +26 -3
  10. package/dist/backend.js.map +1 -1
  11. package/dist/bookmarks.d.ts +30 -0
  12. package/dist/bookmarks.d.ts.map +1 -0
  13. package/dist/bookmarks.js +87 -0
  14. package/dist/bookmarks.js.map +1 -0
  15. package/dist/cli.d.ts.map +1 -1
  16. package/dist/cli.js +291 -61
  17. package/dist/cli.js.map +1 -1
  18. package/dist/client.d.ts +115 -4
  19. package/dist/client.d.ts.map +1 -1
  20. package/dist/client.js +854 -138
  21. package/dist/client.js.map +1 -1
  22. package/dist/cursors.d.ts +30 -0
  23. package/dist/cursors.d.ts.map +1 -0
  24. package/dist/cursors.js +101 -0
  25. package/dist/cursors.js.map +1 -0
  26. package/dist/errors.d.ts +1 -1
  27. package/dist/errors.d.ts.map +1 -1
  28. package/dist/errors.js +2 -0
  29. package/dist/errors.js.map +1 -1
  30. package/dist/import.d.ts +605 -39
  31. package/dist/import.d.ts.map +1 -1
  32. package/dist/index.d.ts +19 -1
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +11 -1
  35. package/dist/index.js.map +1 -1
  36. package/dist/mcp/index.d.ts +1 -1
  37. package/dist/mcp/index.d.ts.map +1 -1
  38. package/dist/mcp/index.js +329 -22
  39. package/dist/mcp/index.js.map +1 -1
  40. package/dist/mutation-receipts.d.ts +8 -0
  41. package/dist/mutation-receipts.d.ts.map +1 -0
  42. package/dist/mutation-receipts.js +2 -0
  43. package/dist/mutation-receipts.js.map +1 -0
  44. package/dist/notifications.d.ts +73 -0
  45. package/dist/notifications.d.ts.map +1 -0
  46. package/dist/notifications.js +309 -0
  47. package/dist/notifications.js.map +1 -0
  48. package/dist/participation-repository.d.ts +46 -0
  49. package/dist/participation-repository.d.ts.map +1 -0
  50. package/dist/participation-repository.js +296 -0
  51. package/dist/participation-repository.js.map +1 -0
  52. package/dist/participation.d.ts +97 -0
  53. package/dist/participation.d.ts.map +1 -0
  54. package/dist/participation.js +10 -0
  55. package/dist/participation.js.map +1 -0
  56. package/dist/policy.d.ts +31 -0
  57. package/dist/policy.d.ts.map +1 -0
  58. package/dist/policy.js +71 -0
  59. package/dist/policy.js.map +1 -0
  60. package/dist/ports/repository.d.ts +23 -0
  61. package/dist/ports/repository.d.ts.map +1 -1
  62. package/dist/profiles.d.ts +12 -0
  63. package/dist/profiles.d.ts.map +1 -1
  64. package/dist/profiles.js +2 -1
  65. package/dist/profiles.js.map +1 -1
  66. package/dist/projections.d.ts.map +1 -1
  67. package/dist/projections.js +29 -11
  68. package/dist/projections.js.map +1 -1
  69. package/dist/remote.d.ts +79 -0
  70. package/dist/remote.d.ts.map +1 -1
  71. package/dist/remote.js +65 -6
  72. package/dist/remote.js.map +1 -1
  73. package/dist/schemas.d.ts +746 -58
  74. package/dist/schemas.d.ts.map +1 -1
  75. package/dist/schemas.js +114 -16
  76. package/dist/schemas.js.map +1 -1
  77. package/dist/search.d.ts +25 -0
  78. package/dist/search.d.ts.map +1 -0
  79. package/dist/search.js +2 -0
  80. package/dist/search.js.map +1 -0
  81. package/dist/service.d.ts +89 -0
  82. package/dist/service.d.ts.map +1 -1
  83. package/dist/storage.d.ts +38 -3
  84. package/dist/storage.d.ts.map +1 -1
  85. package/dist/storage.js +494 -365
  86. package/dist/storage.js.map +1 -1
  87. package/dist/types.d.ts +65 -34
  88. package/dist/types.d.ts.map +1 -1
  89. package/docs/cli.md +6 -0
  90. package/docs/human-participation.md +20 -0
  91. package/docs/mcp.md +2 -0
  92. package/docs/storage-format.md +4 -15
  93. package/openapi/synomem-v1.yaml +187 -10
  94. package/package.json +2 -1
  95. package/skills/synomem/SKILL.md +2 -0
  96. package/src/actor-directory.ts +87 -0
  97. package/src/backend.ts +29 -3
  98. package/src/bookmarks.ts +127 -0
  99. package/src/cli.ts +941 -546
  100. package/src/client.ts +1143 -279
  101. package/src/cursors.ts +132 -0
  102. package/src/errors.ts +2 -0
  103. package/src/index.ts +50 -0
  104. package/src/mcp/index.ts +412 -96
  105. package/src/mutation-receipts.ts +7 -0
  106. package/src/notifications.ts +483 -0
  107. package/src/participation-repository.ts +406 -0
  108. package/src/participation.ts +103 -0
  109. package/src/policy.ts +109 -0
  110. package/src/ports/repository.ts +33 -0
  111. package/src/profiles.ts +2 -1
  112. package/src/projections.ts +49 -48
  113. package/src/remote.ts +227 -37
  114. package/src/schemas.ts +116 -16
  115. package/src/search.ts +24 -0
  116. package/src/service.ts +122 -8
  117. package/src/storage.ts +1039 -697
  118. package/src/types.ts +67 -32
@@ -0,0 +1,309 @@
1
+ import { cursorFilter } from './cursors.js';
2
+ import { SynomemError } from './errors.js';
3
+ export const notificationTables = `
4
+ CREATE TABLE notification_sequences(workspace_id TEXT PRIMARY KEY,last_sequence BIGINT NOT NULL);
5
+ CREATE TABLE inbox_notifications (
6
+ workspace_id TEXT NOT NULL, notification_id TEXT NOT NULL, event_id TEXT NOT NULL,root_id TEXT NOT NULL,reply_id TEXT,
7
+ recipient_kind TEXT NOT NULL,recipient_id TEXT NOT NULL,source_sequence BIGINT NOT NULL,delivery_sequence BIGINT NOT NULL,
8
+ reasons_json TEXT NOT NULL,published_at TEXT NOT NULL,read_at TEXT,dismissed_at TEXT,
9
+ PRIMARY KEY(workspace_id,notification_id),UNIQUE(workspace_id,event_id,recipient_kind,recipient_id),UNIQUE(workspace_id,delivery_sequence)
10
+ );
11
+ CREATE INDEX inbox_actor_delivery ON inbox_notifications(workspace_id,recipient_kind,recipient_id,delivery_sequence);
12
+ CREATE INDEX inbox_retention ON inbox_notifications(workspace_id,published_at);
13
+ CREATE TABLE notification_fanout_jobs (
14
+ workspace_id TEXT NOT NULL,event_id TEXT NOT NULL,root_id TEXT NOT NULL,source_sequence BIGINT NOT NULL,
15
+ state TEXT NOT NULL CHECK(state IN ('pending','complete')),created_at TEXT NOT NULL,completed_at TEXT,
16
+ PRIMARY KEY(workspace_id,event_id)
17
+ );
18
+ CREATE TABLE notification_fanout_targets (
19
+ workspace_id TEXT NOT NULL,event_id TEXT NOT NULL,recipient_kind TEXT NOT NULL,recipient_id TEXT NOT NULL,
20
+ state TEXT NOT NULL CHECK(state IN ('pending','delivered','suppressed')),completed_at TEXT,
21
+ PRIMARY KEY(workspace_id,event_id,recipient_kind,recipient_id),
22
+ FOREIGN KEY(workspace_id,event_id) REFERENCES notification_fanout_jobs(workspace_id,event_id)
23
+ );
24
+ CREATE INDEX fanout_pending ON notification_fanout_targets(workspace_id,state,event_id,recipient_kind,recipient_id);
25
+ `;
26
+ const value = (entry) => String(entry);
27
+ const actionable = `((i.kind='memo' AND i.status='unread' AND i.recipient_kind=n.recipient_kind AND i.recipient_id=n.recipient_id) OR (i.kind='task' AND i.status='assigned' AND i.assignee_kind=n.recipient_kind AND i.assignee_id=n.recipient_id) OR (i.kind='kudos' AND i.status='unacknowledged' AND i.recipient_kind=n.recipient_kind AND i.recipient_id=n.recipient_id))`;
28
+ export class SqlNotificationRepository {
29
+ sql;
30
+ constructor(sql) {
31
+ this.sql = sql;
32
+ }
33
+ get workspace() {
34
+ return this.sql.workspaceId;
35
+ }
36
+ async publish(eventId, rootId, replyId, source, actor, reasons, at) {
37
+ if (!(await this.sql.canRead(rootId, actor)))
38
+ return;
39
+ const muted = (await this.sql.query('SELECT muted,last_read_sequence FROM thread_members WHERE workspace_id=? AND root_id=? AND actor_kind=? AND actor_id=?', [this.workspace, rootId, actor.kind, actor.id]))[0];
40
+ if (Number(muted?.muted) === 1 &&
41
+ !reasons.includes('direct') &&
42
+ !reasons.includes('intervention') &&
43
+ !reasons.includes('mention'))
44
+ return;
45
+ const read = BigInt(value(muted?.last_read_sequence ?? '0')) >= BigInt(source);
46
+ const max = (await this.sql.query('INSERT INTO notification_sequences(workspace_id,last_sequence) VALUES(?,1) ON CONFLICT(workspace_id) DO UPDATE SET last_sequence=notification_sequences.last_sequence+1 RETURNING last_sequence AS next', [this.workspace]))[0];
47
+ // Deterministic tuple-derived ID; no content or endpoint secrets enter notification state.
48
+ const id = `ntf-${cursorFilter({ eventId, actor })}`;
49
+ await this.sql.execute(`INSERT INTO inbox_notifications(workspace_id,notification_id,event_id,root_id,reply_id,recipient_kind,recipient_id,source_sequence,delivery_sequence,reasons_json,published_at,read_at) VALUES(?,?,?,?,?,?,?,?,?,?,?,?) ON CONFLICT(workspace_id,event_id,recipient_kind,recipient_id) DO NOTHING`, [
50
+ this.workspace,
51
+ id,
52
+ eventId,
53
+ rootId,
54
+ replyId ?? null,
55
+ actor.kind,
56
+ actor.id,
57
+ source,
58
+ value(max.next),
59
+ JSON.stringify(reasons),
60
+ at,
61
+ read ? at : null,
62
+ ]);
63
+ }
64
+ async apply(event, sequence) {
65
+ if (event.type.startsWith('reaction.') || event.type === 'reply.deleted')
66
+ return;
67
+ const rootId = 'rootId' in event
68
+ ? event.rootId
69
+ : /^(memo|task|kudos|post|note|todo)\./.test(event.type)
70
+ ? event.aggregateId
71
+ : undefined;
72
+ if (!rootId)
73
+ return;
74
+ const row = (await this.sql.query('SELECT payload FROM events WHERE workspace_id=? AND aggregate_id=? ORDER BY sequence LIMIT 1', [this.workspace, rootId]))[0];
75
+ if (!row)
76
+ return;
77
+ const root = (typeof row.payload === 'string' ? JSON.parse(row.payload) : row.payload);
78
+ const targets = [];
79
+ const add = (actor) => {
80
+ if (actor &&
81
+ actor.kind !== 'system' &&
82
+ !targets.some((target) => target.kind === actor.kind && target.id === actor.id))
83
+ targets.push({ kind: actor.kind, id: actor.id });
84
+ };
85
+ add(root.actor);
86
+ if ('recipient' in root)
87
+ add(root.recipient);
88
+ if ('assignee' in root)
89
+ add(root.assignee);
90
+ if ('owner' in root)
91
+ add(root.owner);
92
+ if (root.id === event.id)
93
+ for (const actor of targets)
94
+ await this.sql.execute(`INSERT INTO thread_members(workspace_id,root_id,actor_kind,actor_id,following,muted,joined_sequence) VALUES(?,?,?,?,1,0,?) ON CONFLICT(workspace_id,root_id,actor_kind,actor_id) DO NOTHING`, [this.workspace, rootId, actor.kind, actor.id, sequence.toString()]);
95
+ const immediate = new Map();
96
+ const notify = (actor, reason, self = false) => {
97
+ if (!actor ||
98
+ actor.kind === 'system' ||
99
+ (!self && actor.kind === event.actor.kind && actor.id === event.actor.id))
100
+ return;
101
+ const key = `${actor.kind}:${actor.id}`;
102
+ const entry = immediate.get(key) ?? {
103
+ actor: { kind: actor.kind, id: actor.id },
104
+ reasons: new Set(),
105
+ };
106
+ entry.reasons.add(reason);
107
+ immediate.set(key, entry);
108
+ };
109
+ if (event.type === 'memo.sent')
110
+ notify(event.recipient, 'direct', true);
111
+ else if (event.type === 'kudos.given')
112
+ notify(event.recipient, 'direct');
113
+ else if (event.type === 'task.created')
114
+ notify(event.assignee, 'direct');
115
+ else if (event.type === 'memo.read' ||
116
+ event.type === 'kudos.acknowledged' ||
117
+ event.type === 'post.acknowledged' ||
118
+ event.type === 'post.acknowledgment.withdrawn')
119
+ notify(root.actor, 'response');
120
+ else if (event.type.startsWith('task.'))
121
+ for (const target of targets)
122
+ notify(target, 'lifecycle');
123
+ if (event.intervention)
124
+ for (const target of targets)
125
+ if (target.kind === 'agent')
126
+ notify(target, 'intervention');
127
+ if (event.type === 'post.created' || event.type === 'post.edited') {
128
+ const prior = event.type === 'post.edited'
129
+ ? (await this.sql.query("SELECT payload FROM events WHERE workspace_id=? AND aggregate_id=? AND type IN ('post.created','post.edited') AND id<>? ORDER BY sequence DESC LIMIT 1", [this.workspace, rootId, event.id]))[0]
130
+ : undefined;
131
+ const previous = prior
132
+ ? (typeof prior.payload === 'string' ? JSON.parse(prior.payload) : prior.payload)
133
+ : undefined;
134
+ for (const mention of event.mentions ?? [])
135
+ if (!previous?.mentions?.some((actor) => actor.kind === mention.kind && actor.id === mention.id))
136
+ notify(mention, 'mention');
137
+ }
138
+ if (event.type === 'reply.created') {
139
+ for (const target of targets)
140
+ notify(target, 'reply');
141
+ for (const mention of event.mentions)
142
+ notify(mention, 'mention');
143
+ if (event.parentId) {
144
+ const parent = (await this.sql.query('SELECT author_json FROM replies_current WHERE workspace_id=? AND reply_id=?', [this.workspace, event.parentId]))[0];
145
+ if (parent)
146
+ notify(JSON.parse(value(parent.author_json)), 'reply');
147
+ }
148
+ }
149
+ for (const recipient of immediate.values())
150
+ await this.publish(event.id, rootId, event.type === 'reply.created' ? event.id : undefined, sequence.toString(), recipient.actor, [...recipient.reasons].sort(), event.createdAt);
151
+ if (event.type === 'reply.created') {
152
+ await this.sql.execute(`INSERT INTO notification_fanout_jobs(workspace_id,event_id,root_id,source_sequence,state,created_at) VALUES(?,?,?,?,'pending',?) ON CONFLICT(workspace_id,event_id) DO NOTHING`, [this.workspace, event.id, rootId, sequence.toString(), event.createdAt]);
153
+ await this.sql.execute(`INSERT INTO notification_fanout_targets(workspace_id,event_id,recipient_kind,recipient_id,state) SELECT m.workspace_id,?,m.actor_kind,m.actor_id,'pending' FROM thread_members m WHERE m.workspace_id=? AND m.root_id=? AND m.following=1 AND m.muted=0 AND NOT(m.actor_kind=? AND m.actor_id=?) AND NOT EXISTS(SELECT 1 FROM inbox_notifications n WHERE n.workspace_id=m.workspace_id AND n.event_id=? AND n.recipient_kind=m.actor_kind AND n.recipient_id=m.actor_id) ON CONFLICT(workspace_id,event_id,recipient_kind,recipient_id) DO NOTHING`, [event.id, this.workspace, rootId, event.actor.kind, event.actor.id, event.id]);
154
+ }
155
+ }
156
+ async drain(limit = 200) {
157
+ const targets = await this.sql.query(`SELECT t.event_id,t.recipient_kind,t.recipient_id,j.root_id,j.source_sequence FROM notification_fanout_targets t JOIN notification_fanout_jobs j ON j.workspace_id=t.workspace_id AND j.event_id=t.event_id WHERE t.workspace_id=? AND t.state='pending' ORDER BY j.source_sequence,t.recipient_kind,t.recipient_id LIMIT ?`, [this.workspace, Math.min(200, Math.max(1, limit))]);
158
+ let delivered = 0, suppressed = 0;
159
+ const at = new Date().toISOString();
160
+ for (const target of targets) {
161
+ const actor = {
162
+ kind: value(target.recipient_kind),
163
+ id: value(target.recipient_id),
164
+ };
165
+ const muted = (await this.sql.query('SELECT muted,following FROM thread_members WHERE workspace_id=? AND root_id=? AND actor_kind=? AND actor_id=?', [this.workspace, value(target.root_id), actor.kind, actor.id]))[0];
166
+ const allowed = Number(muted?.muted) !== 1 &&
167
+ Number(muted?.following) === 1 &&
168
+ (await this.sql.canRead(value(target.root_id), actor));
169
+ if (allowed) {
170
+ await this.publish(value(target.event_id), value(target.root_id), value(target.event_id), value(target.source_sequence), actor, ['reply'], at);
171
+ delivered++;
172
+ }
173
+ else
174
+ suppressed++;
175
+ await this.sql.execute('UPDATE notification_fanout_targets SET state=?,completed_at=? WHERE workspace_id=? AND event_id=? AND recipient_kind=? AND recipient_id=?', [
176
+ allowed ? 'delivered' : 'suppressed',
177
+ at,
178
+ this.workspace,
179
+ value(target.event_id),
180
+ actor.kind,
181
+ actor.id,
182
+ ]);
183
+ }
184
+ await this.sql.execute(`UPDATE notification_fanout_jobs SET state='complete',completed_at=? WHERE workspace_id=? AND state='pending' AND NOT EXISTS(SELECT 1 FROM notification_fanout_targets t WHERE t.workspace_id=notification_fanout_jobs.workspace_id AND t.event_id=notification_fanout_jobs.event_id AND t.state='pending')`, [at, this.workspace]);
185
+ return { delivered, suppressed };
186
+ }
187
+ async list(input, actor) {
188
+ const view = input.view ?? 'default';
189
+ const limit = Math.min(100, Math.max(1, input.limit ?? 20));
190
+ const visibility = this.sql.visibility(actor);
191
+ const codec = this.sql.codec();
192
+ const binding = {
193
+ purpose: 'inbox',
194
+ workspaceId: this.workspace,
195
+ actor,
196
+ filter: cursorFilter({ view }),
197
+ };
198
+ const maximum = value((await this.sql.query('SELECT COALESCE((SELECT last_sequence FROM notification_sequences WHERE workspace_id=?),0) AS max', [this.workspace]))[0].max);
199
+ const position = input.after ? codec.decode(input.after, binding, maximum) : undefined;
200
+ const high = position?.watermark ?? maximum;
201
+ const filter = view === 'unread'
202
+ ? ' AND n.read_at IS NULL'
203
+ : view === 'action_required'
204
+ ? ` AND ${actionable}`
205
+ : view === 'default'
206
+ ? ` AND (n.read_at IS NULL OR ${actionable})`
207
+ : '';
208
+ const rows = await this.sql.query(`SELECT n.*,i.kind AS root_kind,i.title AS root_title,${actionable} AS action_required FROM inbox_notifications n JOIN items_current i ON i.workspace_id=n.workspace_id AND i.item_id=n.root_id WHERE n.workspace_id=? AND n.recipient_kind=? AND n.recipient_id=? AND n.delivery_sequence>? AND n.delivery_sequence<=? AND n.dismissed_at IS NULL AND (${visibility.sql})${filter} ORDER BY n.delivery_sequence LIMIT ?`, [
209
+ this.workspace,
210
+ actor.kind,
211
+ actor.id,
212
+ position?.sequence ?? '0',
213
+ high,
214
+ ...visibility.values,
215
+ limit + 1,
216
+ ]);
217
+ const selected = rows.slice(0, limit);
218
+ const mapped = selected.map((row) => ({
219
+ root: {
220
+ id: value(row.root_id),
221
+ kind: value(row.root_kind),
222
+ title: value(row.root_title),
223
+ },
224
+ id: value(row.notification_id),
225
+ rootId: value(row.root_id),
226
+ ...(row.reply_id ? { replyId: value(row.reply_id) } : {}),
227
+ eventId: value(row.event_id),
228
+ sourceSequence: value(row.source_sequence),
229
+ deliverySequence: value(row.delivery_sequence),
230
+ reasonCodes: JSON.parse(value(row.reasons_json)),
231
+ publishedAt: value(row.published_at),
232
+ read: row.read_at != null,
233
+ dismissed: row.dismissed_at != null,
234
+ actionRequired: row.action_required === true || Number(row.action_required) === 1,
235
+ }));
236
+ const items = [];
237
+ let bytes = 2048;
238
+ for (const entry of mapped) {
239
+ const size = Buffer.byteLength(JSON.stringify(entry));
240
+ if (items.length && bytes + size > 24576)
241
+ break;
242
+ items.push(entry);
243
+ bytes += size;
244
+ }
245
+ const hasMore = rows.length > items.length;
246
+ return {
247
+ items,
248
+ hasMore,
249
+ ...(hasMore
250
+ ? {
251
+ nextCursor: codec.encode(binding, {
252
+ sequence: items.at(-1).deliverySequence,
253
+ watermark: high,
254
+ }),
255
+ }
256
+ : {}),
257
+ watermark: codec.encode({ purpose: 'inbox-read', workspaceId: this.workspace, actor, filter: cursorFilter({}) }, { sequence: high, watermark: high }),
258
+ };
259
+ }
260
+ async read(id, actor) {
261
+ const row = (await this.sql.query('SELECT root_id FROM inbox_notifications WHERE workspace_id=? AND notification_id=? AND recipient_kind=? AND recipient_id=?', [this.workspace, id, actor.kind, actor.id]))[0];
262
+ if (!row || !(await this.sql.canRead(value(row.root_id), actor)))
263
+ throw new SynomemError('ITEM_NOT_FOUND', 'Unknown notification.');
264
+ await this.sql.execute('UPDATE inbox_notifications SET read_at=COALESCE(read_at,?) WHERE workspace_id=? AND notification_id=? AND recipient_kind=? AND recipient_id=?', [new Date().toISOString(), this.workspace, id, actor.kind, actor.id]);
265
+ }
266
+ async dismiss(id, actor) {
267
+ await this.read(id, actor);
268
+ await this.sql.execute('UPDATE inbox_notifications SET dismissed_at=COALESCE(dismissed_at,?) WHERE workspace_id=? AND notification_id=? AND recipient_kind=? AND recipient_id=?', [new Date().toISOString(), this.workspace, id, actor.kind, actor.id]);
269
+ }
270
+ async readThrough(through, actor) {
271
+ const max = value((await this.sql.query('SELECT COALESCE((SELECT last_sequence FROM notification_sequences WHERE workspace_id=?),0) AS max', [this.workspace]))[0].max);
272
+ const position = this.sql
273
+ .codec()
274
+ .decode(through, { purpose: 'inbox-read', workspaceId: this.workspace, actor, filter: cursorFilter({}) }, max);
275
+ const visibility = this.sql.visibility(actor);
276
+ await this.sql.execute(`UPDATE inbox_notifications SET read_at=COALESCE(read_at,?) WHERE workspace_id=? AND recipient_kind=? AND recipient_id=? AND delivery_sequence<=? AND root_id IN(SELECT i.item_id FROM items_current i WHERE i.workspace_id=? AND (${visibility.sql}))`, [
277
+ new Date().toISOString(),
278
+ this.workspace,
279
+ actor.kind,
280
+ actor.id,
281
+ position.sequence,
282
+ this.workspace,
283
+ ...visibility.values,
284
+ ]);
285
+ }
286
+ async metrics() {
287
+ const row = (await this.sql.query(`SELECT COUNT(*) AS pending,MIN(j.created_at) AS oldest FROM notification_fanout_targets t JOIN notification_fanout_jobs j ON j.workspace_id=t.workspace_id AND j.event_id=t.event_id WHERE t.workspace_id=? AND t.state='pending'`, [this.workspace]))[0];
288
+ return { pending: value(row.pending), oldest: row.oldest == null ? null : value(row.oldest) };
289
+ }
290
+ async prune(at) {
291
+ const time = new Date(at).getTime();
292
+ const readBefore = new Date(time - 90 * 86400000).toISOString();
293
+ const unreadBefore = new Date(time - 180 * 86400000).toISOString();
294
+ const rows = await this.sql.query(`SELECT n.notification_id FROM inbox_notifications n JOIN items_current i ON i.workspace_id=n.workspace_id AND i.item_id=n.root_id WHERE n.workspace_id=? AND NOT ${actionable} AND (((n.read_at IS NOT NULL OR n.dismissed_at IS NOT NULL) AND n.published_at<?) OR (n.read_at IS NULL AND n.published_at<?)) ORDER BY n.delivery_sequence LIMIT 1000`, [this.workspace, readBefore, unreadBefore]);
295
+ for (const row of rows)
296
+ await this.sql.execute('DELETE FROM inbox_notifications WHERE workspace_id=? AND notification_id=?', [this.workspace, value(row.notification_id)]);
297
+ // Keep completed jobs as permanent source-event dedupe identities; only target detail expires.
298
+ const expired = await this.sql.query(`SELECT t.event_id,t.recipient_kind,t.recipient_id FROM notification_fanout_targets t JOIN notification_fanout_jobs j ON j.workspace_id=t.workspace_id AND j.event_id=t.event_id WHERE t.workspace_id=? AND t.state!='pending' AND t.completed_at<? AND j.state='complete' LIMIT 1000`, [this.workspace, new Date(time - 30 * 86400000).toISOString()]);
299
+ for (const target of expired)
300
+ await this.sql.execute('DELETE FROM notification_fanout_targets WHERE workspace_id=? AND event_id=? AND recipient_kind=? AND recipient_id=?', [
301
+ this.workspace,
302
+ value(target.event_id),
303
+ value(target.recipient_kind),
304
+ value(target.recipient_id),
305
+ ]);
306
+ return rows.length;
307
+ }
308
+ }
309
+ //# sourceMappingURL=notifications.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notifications.js","sourceRoot":"","sources":["../src/notifications.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ import type { SignedCursorCodec } from './cursors.js';
2
+ import type { ActorRef } from './policy.js';
3
+ import type { ActorIdentity, SynomemEvent } from './types.js';
4
+ import type { ParticipationRepository, ReplyRecord, ThreadInput, ThreadPage, ReactionCode, ReactionSummary } from './participation.js';
5
+ export interface ParticipationBudgets {
6
+ reply: {
7
+ minute: number;
8
+ hour: number;
9
+ workspaceDay: number;
10
+ };
11
+ reaction: {
12
+ minute: number;
13
+ hour: number;
14
+ workspaceDay: number;
15
+ };
16
+ }
17
+ export declare const defaultParticipationBudgets: ParticipationBudgets;
18
+ export declare function validateParticipationBudgets(budgets: ParticipationBudgets): ParticipationBudgets;
19
+ /** Trusted storage adapter. SQL uses positional question marks; hosted adapters bind parameters. */
20
+ export interface ParticipationSql {
21
+ query(sql: string, values: unknown[]): Promise<Array<Record<string, unknown>>>;
22
+ execute(sql: string, values: unknown[]): Promise<void>;
23
+ codec(): SignedCursorCodec;
24
+ workspaceId: string;
25
+ budgets?: ParticipationBudgets;
26
+ }
27
+ export declare const participationTables = "\nCREATE TABLE replies_current (\n workspace_id TEXT NOT NULL, reply_id TEXT NOT NULL, root_id TEXT NOT NULL, parent_id TEXT,\n author_json TEXT NOT NULL, created_at TEXT NOT NULL, created_sequence BIGINT NOT NULL,\n updated_sequence BIGINT NOT NULL, version INTEGER NOT NULL, body TEXT, mentions_json TEXT NOT NULL,\n deleted_at TEXT, PRIMARY KEY(workspace_id,reply_id), UNIQUE(workspace_id,root_id,reply_id),\n FOREIGN KEY(workspace_id,root_id,parent_id) REFERENCES replies_current(workspace_id,root_id,reply_id)\n);\nCREATE INDEX replies_root_sequence ON replies_current(workspace_id,root_id,created_sequence,reply_id);\nCREATE INDEX replies_root_changes ON replies_current(workspace_id,root_id,updated_sequence,reply_id);\nCREATE TABLE thread_entries (\n workspace_id TEXT NOT NULL, root_id TEXT NOT NULL, event_id TEXT NOT NULL, sequence BIGINT NOT NULL,\n PRIMARY KEY(workspace_id,event_id)\n);\nCREATE INDEX thread_entries_root_sequence ON thread_entries(workspace_id,root_id,sequence,event_id);\nCREATE TABLE thread_members (\n workspace_id TEXT NOT NULL, root_id TEXT NOT NULL, actor_kind TEXT NOT NULL, actor_id TEXT NOT NULL,\n following INTEGER NOT NULL CHECK(following IN (0,1)), muted INTEGER NOT NULL CHECK(muted IN (0,1)),\n last_read_sequence BIGINT NOT NULL DEFAULT 0, joined_sequence BIGINT NOT NULL DEFAULT 0,\n PRIMARY KEY(workspace_id,root_id,actor_kind,actor_id)\n);\nCREATE TABLE reactions_current (\n workspace_id TEXT NOT NULL, target_id TEXT NOT NULL, root_id TEXT NOT NULL,\n actor_kind TEXT NOT NULL, actor_id TEXT NOT NULL, code TEXT NOT NULL,\n event_id TEXT NOT NULL, created_at TEXT NOT NULL,\n PRIMARY KEY(workspace_id,target_id,actor_kind,actor_id,code)\n);\nCREATE INDEX reactions_root ON reactions_current(workspace_id,root_id,target_id);\nCREATE TABLE record_mentions (\n workspace_id TEXT NOT NULL,source_event_id TEXT NOT NULL,root_id TEXT NOT NULL,reply_id TEXT,\n mentioned_kind TEXT NOT NULL,mentioned_id TEXT NOT NULL,\n PRIMARY KEY(workspace_id,source_event_id,mentioned_kind,mentioned_id)\n);\nCREATE INDEX record_mentions_root ON record_mentions(workspace_id,root_id,source_event_id);\nCREATE TABLE mutation_budgets (\n workspace_id TEXT NOT NULL, bucket_key TEXT NOT NULL, bucket_start BIGINT NOT NULL,\n window_seconds INTEGER NOT NULL, used INTEGER NOT NULL,\n PRIMARY KEY(workspace_id,bucket_key,bucket_start,window_seconds)\n);\nCREATE INDEX mutation_budgets_retention ON mutation_budgets(workspace_id,bucket_start);\n";
28
+ export declare class SqlParticipationRepository implements ParticipationRepository {
29
+ private readonly sql;
30
+ constructor(sql: ParticipationSql);
31
+ private get workspace();
32
+ apply(event: SynomemEvent, sequence: bigint, replay?: boolean): Promise<void>;
33
+ getReply(id: string): Promise<ReplyRecord | undefined>;
34
+ thread(input: ThreadInput, actor: ActorIdentity, purpose?: string): Promise<ThreadPage>;
35
+ changes(input: ThreadInput, actor: ActorIdentity): Promise<ThreadPage>;
36
+ read(rootId: string, through: string, actor: ActorRef): Promise<void>;
37
+ hasReaction(targetId: string, actor: ActorRef, code: ReactionCode): Promise<boolean>;
38
+ reactions(targetId: string, actor: ActorRef): Promise<ReactionSummary>;
39
+ subscription(rootId: string, actor: ActorRef, following: boolean, muted: boolean): Promise<void>;
40
+ reserveBudget(kind: 'reply' | 'reaction', actor: ActorRef, at: string): Promise<void>;
41
+ }
42
+ export declare function participationStatements(event: SynomemEvent, sequence: bigint, workspace: string): Array<{
43
+ sql: string;
44
+ values: unknown[];
45
+ }>;
46
+ //# sourceMappingURL=participation-repository.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"participation-repository.d.ts","sourceRoot":"","sources":["../src/participation-repository.ts"],"names":[],"mappings":"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"}