@voltro/plugin-comments 0.84.0 → 0.85.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/dist/index.js CHANGED
@@ -1,94 +1,144 @@
1
- import { CommentAccessRefused as e, CommentNotFound as t, CommentNotYours as n } from "./errors.js";
2
- import { a as r, c as i, d as a, f as o, i as s, l as c, m as l, n as u, o as d, p as f, r as p, s as m, t as h, u as g } from "./rpc-DlFs6xXs.js";
3
- import { Effect as _, Option as v } from "effect";
4
- import { and as y, contains as b, eq as x, generateId as S, id as C, inSet as w, json as T, neq as E, or as D, table as O, text as k, timestamp as A } from "@voltro/database";
5
- import { definePlugin as j, pluginInstanceName as M, publishReactivity as N } from "@voltro/protocol";
6
- import { NotificationService as P } from "@voltro/plugin-notifications";
1
+ import { CommentAccessRefused as e, CommentNotFound as t, CommentNotYours as n, CommentStatusRefused as r } from "./errors.js";
2
+ import { a as i, c as a, d as o, f as s, h as c, i as l, l as u, m as d, n as f, o as p, p as m, r as h, s as g, t as _, u as v } from "./rpc-BTQBcdhT.js";
3
+ import { Effect as y, Option as b } from "effect";
4
+ import { and as x, contains as S, eq as C, generateId as w, id as T, inSet as E, json as D, neq as O, or as k, table as A, text as j, timestamp as M } from "@voltro/database";
5
+ import { definePlugin as N, pluginInstanceName as P, publishReactivity as F } from "@voltro/protocol";
6
+ import { NotificationService as I } from "@voltro/plugin-notifications";
7
7
  //#region src/index.ts
8
- var F = O("_voltro_comment_threads", {
9
- id: C({ prefix: "cmtthr" }),
10
- anchor: k(),
11
- quote: T().nullable(),
12
- status: k().default("open"),
13
- createdBy: k(),
14
- createdAt: A().default("now"),
15
- tenantId: k().nullable()
16
- }).index("byThreadAnchor", ["anchor", "tenantId"]), I = O("_voltro_comments", {
17
- id: C({ prefix: "cmt" }),
18
- threadId: k(),
19
- body: k(),
20
- authorSubjectId: k(),
21
- mentions: T(),
22
- editedAt: A().nullable(),
23
- createdAt: A().default("now"),
24
- tenantId: k().nullable()
25
- }).index("byCommentThread", ["threadId"]), L = O("_voltro_comment_reactions", {
26
- id: C({ prefix: "cmtrx" }),
27
- commentId: k(),
28
- subjectId: k(),
29
- emoji: k()
8
+ var L = A("_voltro_comment_threads", {
9
+ id: T({ prefix: "cmtthr" }),
10
+ anchor: j(),
11
+ quote: D().nullable(),
12
+ kind: j().default("discussion"),
13
+ status: j().default("open"),
14
+ createdBy: j(),
15
+ createdAt: M().default("now"),
16
+ tenantId: j().nullable()
17
+ }).index("byThreadAnchor", ["anchor", "tenantId"]), R = A("_voltro_comments", {
18
+ id: T({ prefix: "cmt" }),
19
+ threadId: j(),
20
+ body: j(),
21
+ authorSubjectId: j(),
22
+ mentions: D(),
23
+ editedAt: M().nullable(),
24
+ createdAt: M().default("now"),
25
+ tenantId: j().nullable()
26
+ }).index("byCommentThread", ["threadId"]), z = A("_voltro_comment_thread_events", {
27
+ id: T({ prefix: "cmtevt" }),
28
+ threadId: j(),
29
+ fromStatus: j().nullable(),
30
+ toStatus: j(),
31
+ actor: j(),
32
+ reason: j().nullable(),
33
+ at: M().default("now"),
34
+ tenantId: j().nullable()
35
+ }).index("byThreadEvents", ["threadId"]), B = A("_voltro_comment_reactions", {
36
+ id: T({ prefix: "cmtrx" }),
37
+ commentId: j(),
38
+ subjectId: j(),
39
+ emoji: j()
30
40
  }).unique("byReactionIdentity", [
31
41
  "commentId",
32
42
  "subjectId",
33
43
  "emoji"
34
- ]).index("byReactionComment", ["commentId"]), R = O("_voltro_comment_reads", {
35
- id: C({ prefix: "cmtread" }),
36
- threadId: k(),
37
- subjectId: k(),
38
- lastReadAt: A()
39
- }).unique("byReadThreadSubject", ["threadId", "subjectId"]), z = h, B = (e) => {
44
+ ]).index("byReactionComment", ["commentId"]), V = A("_voltro_comment_reads", {
45
+ id: T({ prefix: "cmtread" }),
46
+ threadId: j(),
47
+ subjectId: j(),
48
+ lastReadAt: M()
49
+ }).unique("byReadThreadSubject", ["threadId", "subjectId"]), H = _, U = (e) => {
40
50
  let t = e.request.subject ?? {};
41
51
  return {
42
52
  id: t.id ?? null,
43
53
  tenantId: t.tenantId ?? null,
44
54
  scopes: t.scopes ?? []
45
55
  };
46
- }, V = (o = {}) => {
47
- let l = M({
48
- base: z,
49
- alias: o.alias,
50
- instance: o.name
51
- }), f, h, C = async (e, t, n) => f === void 0 ? [] : f.query({
56
+ }, W = (c = {}) => {
57
+ let d = P({
58
+ base: H,
59
+ alias: c.alias,
60
+ instance: c.name
61
+ }), m, _, T = async (e, t, n) => m === void 0 ? [] : m.query({
52
62
  table: e,
53
63
  predicate: t,
54
64
  order: [],
55
65
  take: n,
56
66
  skip: void 0,
57
67
  projection: void 0
58
- }), T = () => {
59
- N(h, u);
60
- }, O = (t, n) => _.gen(function* () {
61
- let r = B(n);
62
- return o.access?.viaEntity === void 0 ? o.access?.scope === void 0 ? yield* _.fail(new e({
68
+ }), D = async (e, t, n, r) => {
69
+ let i = [], a = [...new Set(n)];
70
+ for (let n = 0; n < a.length; n += 500) {
71
+ let o = E(t, a.slice(n, n + 500));
72
+ i.push(...await T(e, r === void 0 ? o : x(o, r)));
73
+ }
74
+ return i;
75
+ }, A = (e, t) => {
76
+ let n = /* @__PURE__ */ new Map();
77
+ for (let r of e) {
78
+ let e = String(r[t]), i = n.get(e);
79
+ i === void 0 ? n.set(e, [r]) : i.push(r);
80
+ }
81
+ return n;
82
+ }, j = () => {
83
+ F(_, h);
84
+ }, M = (t, n) => y.gen(function* () {
85
+ let r = U(n);
86
+ return c.access?.viaEntity === void 0 ? c.access?.scope === void 0 ? yield* y.fail(new e({
63
87
  anchor: t,
64
88
  reason: "no access rule declared — set commentsPlugin({ access: { viaEntity } }) or { access: { scope } }"
65
- })) : r.scopes.includes(o.access.scope) ? void 0 : yield* _.fail(new e({
89
+ })) : r.scopes.includes(c.access.scope) ? void 0 : yield* y.fail(new e({
66
90
  anchor: t,
67
- reason: `missing scope '${o.access.scope}'`
68
- })) : (yield* _.promise(async () => {
91
+ reason: `missing scope '${c.access.scope}'`
92
+ })) : (yield* y.promise(async () => {
69
93
  try {
70
- return await o.access.viaEntity({
94
+ return await c.access.viaEntity({
71
95
  anchor: t,
72
96
  subject: r,
73
- store: f
97
+ store: m
74
98
  });
75
99
  } catch {
76
100
  return !1;
77
101
  }
78
- })) ? void 0 : yield* _.fail(new e({
102
+ })) ? void 0 : yield* y.fail(new e({
79
103
  anchor: t,
80
104
  reason: "the anchored entity is not readable by this caller (or no longer available)"
81
105
  }));
82
- }), k = async (e) => (await C("_voltro_comment_threads", x("id", e), 1))[0], A = (e) => e instanceof Date ? e.toISOString() : typeof e == "string" ? e : "", V = (e) => _.gen(function* () {
106
+ }), W = async (e) => (await T("_voltro_comment_threads", C("id", e), 1))[0], G = (e) => ({
107
+ id: String(e.id),
108
+ anchor: String(e.anchor),
109
+ kind: f.includes(String(e.kind)) ? e.kind : "discussion",
110
+ status: e.status === "resolved" ? "resolved" : "open",
111
+ createdBy: String(e.createdBy)
112
+ }), K = (e, t) => c.visibleTo === void 0 || c.visibleTo({
113
+ thread: G(e),
114
+ subject: U(t)
115
+ }), q = async (e, t) => {
116
+ let n = await W(e);
117
+ return n !== void 0 && K(n, t) ? n : void 0;
118
+ }, J = async (e, t) => {
119
+ await e.insert("_voltro_comment_thread_events", {
120
+ id: w({
121
+ kind: "typeid",
122
+ prefix: "cmtevt"
123
+ }, "_voltro_comment_thread_events"),
124
+ threadId: t.threadId,
125
+ fromStatus: t.from,
126
+ toStatus: t.to,
127
+ actor: t.actor,
128
+ reason: t.reason,
129
+ at: (/* @__PURE__ */ new Date()).toISOString(),
130
+ tenantId: t.tenantId
131
+ });
132
+ }, Y = (e) => e instanceof Date ? e.toISOString() : typeof e == "string" ? e : "", X = (e) => y.gen(function* () {
83
133
  if (e.mentions.length === 0) return;
84
- let t = yield* _.serviceOption(P);
85
- if (v.isNone(t)) {
86
- yield* _.logInfo("comments: mention recorded without notification — @voltro/plugin-notifications is not configured");
134
+ let t = yield* y.serviceOption(I);
135
+ if (b.isNone(t)) {
136
+ yield* y.logInfo("comments: mention recorded without notification — @voltro/plugin-notifications is not configured");
87
137
  return;
88
138
  }
89
- for (let n of e.mentions) n !== e.author && (yield* _.promise(() => t.value.send({
139
+ for (let n of e.mentions) n !== e.author && (yield* y.promise(() => t.value.send({
90
140
  to: n,
91
- category: o.mentionCategory ?? "mention",
141
+ category: c.mentionCategory ?? "mention",
92
142
  title: "You were mentioned",
93
143
  body: e.body.length > 200 ? `${e.body.slice(0, 200)}…` : e.body,
94
144
  data: {
@@ -97,191 +147,261 @@ var F = O("_voltro_comment_threads", {
97
147
  },
98
148
  tenantId: e.tenantId
99
149
  }).then(() => void 0).catch(() => void 0)));
100
- }), H = async (e, t) => {
101
- if (e.length === 0 || o.resolveMentions === void 0) return [];
150
+ }), Z = async (e, t) => {
151
+ if (e.length === 0 || c.resolveMentions === void 0) return [];
102
152
  let n = [];
103
153
  for (let r of [...new Set(e)].slice(0, 20)) {
104
- let e = (await o.resolveMentions({
154
+ let e = (await c.resolveMentions({
105
155
  query: r,
106
156
  subject: t
107
157
  }).catch(() => [])).find((e) => e.subjectId === r);
108
- e !== void 0 && (o.crossTenant === !0 || e.tenantId === void 0 || e.tenantId === null || e.tenantId === t.tenantId) && n.push(r);
158
+ e !== void 0 && (c.crossTenant === !0 || e.tenantId === void 0 || e.tenantId === null || e.tenantId === t.tenantId) && n.push(r);
109
159
  }
110
160
  return n;
111
- }, U = async (e, t, n) => {
112
- let r = await C("_voltro_comment_threads", y(x("anchor", e), x("tenantId", t))), i = [];
113
- for (let e of r) {
114
- let t = [...await C("_voltro_comments", x("threadId", String(e.id)))];
115
- t.sort((e, t) => A(e.createdAt) < A(t.createdAt) ? -1 : 1);
116
- let r = n === null ? [] : await C("_voltro_comment_reads", y(x("threadId", String(e.id)), x("subjectId", n)), 1), a = r[0] ? A(r[0].lastReadAt) : "", o = [];
161
+ }, Q = async (e, t) => {
162
+ let { tenantId: n, id: r } = U(t), i = (await T("_voltro_comment_threads", x(C("anchor", e), C("tenantId", n)))).filter((e) => K(e, t));
163
+ if (i.length === 0) return [];
164
+ let a = i.map((e) => String(e.id)), [o, s, c] = await Promise.all([
165
+ D("_voltro_comments", "threadId", a),
166
+ r === null ? Promise.resolve([]) : D("_voltro_comment_reads", "threadId", a, C("subjectId", r)),
167
+ D("_voltro_comment_thread_events", "threadId", a)
168
+ ]), l = A(o, "threadId"), u = A(s, "threadId"), d = A(c, "threadId"), f = A(await D("_voltro_comment_reactions", "commentId", o.map((e) => String(e.id))), "commentId"), p = [];
169
+ for (let e of i) {
170
+ let t = l.get(String(e.id)) ?? [];
171
+ t.sort((e, t) => Y(e.createdAt) < Y(t.createdAt) ? -1 : 1);
172
+ let n = u.get(String(e.id)) ?? [], i = n[0] ? Y(n[0].lastReadAt) : "", a = [];
117
173
  for (let e of t) {
118
- let t = await C("_voltro_comment_reactions", x("commentId", String(e.id))), r = /* @__PURE__ */ new Map();
174
+ let t = f.get(String(e.id)) ?? [], n = /* @__PURE__ */ new Map();
119
175
  for (let e of t) {
120
- let t = r.get(String(e.emoji)) ?? {
176
+ let t = n.get(String(e.emoji)) ?? {
121
177
  count: 0,
122
178
  mine: !1
123
179
  };
124
- t.count += 1, String(e.subjectId) === n && (t.mine = !0), r.set(String(e.emoji), t);
180
+ t.count += 1, String(e.subjectId) === r && (t.mine = !0), n.set(String(e.emoji), t);
125
181
  }
126
- o.push({
182
+ a.push({
127
183
  id: String(e.id),
128
184
  threadId: String(e.threadId),
129
185
  body: String(e.body),
130
186
  authorSubjectId: String(e.authorSubjectId),
131
187
  mentions: e.mentions ?? [],
132
- editedAt: e.editedAt == null ? null : A(e.editedAt),
133
- createdAt: A(e.createdAt),
134
- reactions: [...r.entries()].map(([e, t]) => ({
188
+ editedAt: e.editedAt == null ? null : Y(e.editedAt),
189
+ createdAt: Y(e.createdAt),
190
+ reactions: [...n.entries()].map(([e, t]) => ({
135
191
  emoji: e,
136
192
  count: t.count,
137
193
  mine: t.mine
138
194
  }))
139
195
  });
140
196
  }
141
- i.push({
142
- id: String(e.id),
143
- anchor: String(e.anchor),
197
+ let o = d.get(String(e.id)) ?? [];
198
+ o.sort((e, t) => Y(e.at) < Y(t.at) ? -1 : Y(e.at) > Y(t.at) ? 1 : String(e.id) < String(t.id) ? -1 : 1);
199
+ let s = G(e);
200
+ p.push({
201
+ id: s.id,
202
+ anchor: s.anchor,
203
+ kind: s.kind,
144
204
  quote: e.quote ?? null,
145
- status: e.status === "resolved" ? "resolved" : "open",
205
+ status: s.status,
146
206
  createdBy: String(e.createdBy),
147
- createdAt: A(e.createdAt),
148
- comments: o,
149
- unreadCount: o.filter((e) => String(e.authorSubjectId) !== n && String(e.createdAt) > a).length
207
+ createdAt: Y(e.createdAt),
208
+ comments: a,
209
+ unreadCount: a.filter((e) => String(e.authorSubjectId) !== r && String(e.createdAt) > i).length,
210
+ history: o.map((e) => ({
211
+ from: e.fromStatus === "open" || e.fromStatus === "resolved" ? e.fromStatus : null,
212
+ to: e.toStatus === "resolved" ? "resolved" : "open",
213
+ by: String(e.actor),
214
+ at: Y(e.at),
215
+ reason: e.reason == null ? null : String(e.reason)
216
+ }))
150
217
  });
151
218
  }
152
- return i.sort((e, t) => String(e.createdAt) < String(t.createdAt) ? -1 : 1), i;
153
- }, W = [
219
+ return p.sort((e, t) => String(e.createdAt) < String(t.createdAt) ? -1 : 1), p;
220
+ }, ee = [
154
221
  {
155
- ...m,
222
+ ...a,
156
223
  description: "Threads + comments + reactions + unread for one anchor, live.",
157
- execute: (e, t) => _.gen(function* () {
224
+ execute: (e, t) => y.gen(function* () {
158
225
  let { anchor: n } = e;
159
- yield* O(n, t);
160
- let r = B(t);
161
- return yield* _.promise(() => U(n, r.tenantId, r.id));
226
+ return yield* M(n, t), yield* y.promise(() => Q(n, t));
162
227
  })
163
228
  },
164
229
  {
165
- ...s,
230
+ ...i,
166
231
  description: "Comment on an anchor — a reply into a thread, or a new thread.",
167
- execute: (e, n) => _.gen(function* () {
168
- let r = e;
169
- yield* O(r.anchor, n);
170
- let i = B(n), a = i.id ?? "anonymous", o = f;
171
- if (o === void 0) return yield* _.fail(new t({ id: r.anchor }));
172
- let s = r.threadId;
173
- if (s !== void 0) {
174
- let e = yield* _.promise(() => k(s));
175
- if (e === void 0 || String(e.anchor) !== r.anchor) return yield* _.fail(new t({ id: s }));
176
- } else s = S({
177
- kind: "typeid",
178
- prefix: "cmtthr"
179
- }, "_voltro_comment_threads"), yield* _.promise(() => o.insert("_voltro_comment_threads", {
180
- id: s,
181
- anchor: r.anchor,
182
- quote: r.quote ?? null,
183
- status: "open",
184
- createdBy: a,
185
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
186
- tenantId: i.tenantId
187
- }));
188
- let c = yield* _.promise(() => H(r.mentions ?? [], i)), l = S({
232
+ execute: (n, r) => y.gen(function* () {
233
+ let i = n;
234
+ yield* M(i.anchor, r);
235
+ let a = U(r), o = a.id ?? "anonymous", s = m;
236
+ if (s === void 0) return yield* y.fail(new t({ id: i.anchor }));
237
+ let c = i.threadId;
238
+ if (c !== void 0) {
239
+ let e = yield* y.promise(() => q(c, r));
240
+ if (e === void 0 || String(e.anchor) !== i.anchor) return yield* y.fail(new t({ id: c }));
241
+ } else {
242
+ let t = w({
243
+ kind: "typeid",
244
+ prefix: "cmtthr"
245
+ }, "_voltro_comment_threads");
246
+ c = t;
247
+ let n = i.kind ?? "discussion";
248
+ if (!K({
249
+ id: t,
250
+ anchor: i.anchor,
251
+ kind: n,
252
+ status: "open",
253
+ createdBy: o
254
+ }, r)) return yield* y.fail(new e({
255
+ anchor: i.anchor,
256
+ reason: `a ${n} thread here would not be visible to its author`
257
+ }));
258
+ yield* y.promise(async () => {
259
+ await s.insert("_voltro_comment_threads", {
260
+ id: t,
261
+ anchor: i.anchor,
262
+ quote: i.quote ?? null,
263
+ kind: n,
264
+ status: "open",
265
+ createdBy: o,
266
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
267
+ tenantId: a.tenantId
268
+ }), await J(s, {
269
+ threadId: t,
270
+ from: null,
271
+ to: "open",
272
+ actor: o,
273
+ reason: null,
274
+ tenantId: a.tenantId
275
+ });
276
+ });
277
+ }
278
+ let l = yield* y.promise(() => Z(i.mentions ?? [], a)), u = w({
189
279
  kind: "typeid",
190
280
  prefix: "cmt"
191
281
  }, "_voltro_comments");
192
- return yield* _.promise(() => o.insert("_voltro_comments", {
193
- id: l,
194
- threadId: s,
195
- body: r.body,
196
- authorSubjectId: a,
197
- mentions: c,
282
+ return yield* y.promise(() => s.insert("_voltro_comments", {
283
+ id: u,
284
+ threadId: c,
285
+ body: i.body,
286
+ authorSubjectId: o,
287
+ mentions: l,
198
288
  editedAt: null,
199
289
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
200
- tenantId: i.tenantId
201
- })), T(), yield* V({
202
- mentions: c,
203
- author: a,
204
- anchor: r.anchor,
205
- threadId: s,
206
- body: r.body,
207
- tenantId: i.tenantId
290
+ tenantId: a.tenantId
291
+ })), j(), yield* X({
292
+ mentions: l,
293
+ author: o,
294
+ anchor: i.anchor,
295
+ threadId: c,
296
+ body: i.body,
297
+ tenantId: a.tenantId
208
298
  }), {
209
- threadId: s,
210
- commentId: l
299
+ threadId: c,
300
+ commentId: u
211
301
  };
212
302
  })
213
303
  },
214
304
  {
215
- ...d,
305
+ ...g,
216
306
  description: "Edit your own comment.",
217
- execute: (e, r) => _.gen(function* () {
218
- let i = e, a = (yield* _.promise(() => C("_voltro_comments", x("id", i.commentId), 1)))[0];
219
- if (a === void 0) return yield* _.fail(new t({ id: i.commentId }));
220
- let o = yield* _.promise(() => k(String(a.threadId)));
221
- return o === void 0 ? yield* _.fail(new t({ id: i.commentId })) : (yield* O(String(o.anchor), r), String(a.authorSubjectId) === (B(r).id ?? "anonymous") ? (yield* _.promise(() => f.update("_voltro_comments", i.commentId, {
307
+ execute: (e, r) => y.gen(function* () {
308
+ let i = e, a = (yield* y.promise(() => T("_voltro_comments", C("id", i.commentId), 1)))[0];
309
+ if (a === void 0) return yield* y.fail(new t({ id: i.commentId }));
310
+ let o = yield* y.promise(() => q(String(a.threadId), r));
311
+ return o === void 0 ? yield* y.fail(new t({ id: i.commentId })) : (yield* M(String(o.anchor), r), String(a.authorSubjectId) === (U(r).id ?? "anonymous") ? (yield* y.promise(() => m.update("_voltro_comments", i.commentId, {
222
312
  body: i.body,
223
313
  editedAt: (/* @__PURE__ */ new Date()).toISOString()
224
- })), T(), { ok: !0 }) : yield* _.fail(new n({ id: i.commentId })));
314
+ })), j(), { ok: !0 }) : yield* y.fail(new n({ id: i.commentId })));
225
315
  })
226
316
  },
227
317
  {
228
- ...a,
318
+ ...s,
229
319
  description: "Resolve or reopen a thread.",
230
- execute: (e, n) => _.gen(function* () {
231
- let r = e, i = yield* _.promise(() => k(r.threadId));
232
- return i === void 0 ? yield* _.fail(new t({ id: r.threadId })) : (yield* O(String(i.anchor), n), yield* _.promise(() => f.update("_voltro_comment_threads", r.threadId, { status: r.resolved ? "resolved" : "open" })), T(), { ok: !0 });
320
+ execute: (e, n) => y.gen(function* () {
321
+ let i = e, a = yield* y.promise(() => q(i.threadId, n));
322
+ if (a === void 0) return yield* y.fail(new t({ id: i.threadId }));
323
+ yield* M(String(a.anchor), n);
324
+ let o = G(a), s = i.resolved ? "resolved" : "open";
325
+ if (o.status === s) return { ok: !0 };
326
+ let l = U(n);
327
+ if (c.statusPolicy !== void 0 && !(yield* y.promise(async () => {
328
+ try {
329
+ return await c.statusPolicy({
330
+ thread: o,
331
+ to: s,
332
+ subject: l,
333
+ store: m
334
+ });
335
+ } catch {
336
+ return !1;
337
+ }
338
+ }))) return yield* y.fail(new r({
339
+ threadId: i.threadId,
340
+ reason: `the status policy does not let this caller ${i.resolved ? "resolve" : "reopen"} this ${o.kind} thread`
341
+ }));
342
+ let u = i.reason?.trim() ? i.reason.trim() : null;
343
+ return yield* y.promise(async () => {
344
+ await m.update("_voltro_comment_threads", i.threadId, { status: s }), await J(m, {
345
+ threadId: i.threadId,
346
+ from: o.status,
347
+ to: s,
348
+ actor: l.id ?? "anonymous",
349
+ reason: u,
350
+ tenantId: l.tenantId
351
+ });
352
+ }), j(), { ok: !0 };
233
353
  })
234
354
  },
235
355
  {
236
- ...r,
356
+ ...p,
237
357
  description: "Delete your own comment (comments:moderate deletes any).",
238
- execute: (e, r) => _.gen(function* () {
239
- let i = e, a = (yield* _.promise(() => C("_voltro_comments", x("id", i.commentId), 1)))[0];
240
- if (a === void 0) return yield* _.fail(new t({ id: i.commentId }));
241
- let o = yield* _.promise(() => k(String(a.threadId)));
242
- if (o === void 0) return yield* _.fail(new t({ id: i.commentId }));
243
- yield* O(String(o.anchor), r);
244
- let s = B(r);
245
- return String(a.authorSubjectId) !== (s.id ?? "anonymous") && !s.scopes.includes("comments:moderate") ? yield* _.fail(new n({ id: i.commentId })) : (yield* _.promise(async () => {
246
- for (let e of await C("_voltro_comment_reactions", x("commentId", i.commentId))) await f.delete("_voltro_comment_reactions", String(e.id));
247
- await f.delete("_voltro_comments", i.commentId);
248
- }), T(), { ok: !0 });
358
+ execute: (e, r) => y.gen(function* () {
359
+ let i = e, a = (yield* y.promise(() => T("_voltro_comments", C("id", i.commentId), 1)))[0];
360
+ if (a === void 0) return yield* y.fail(new t({ id: i.commentId }));
361
+ let o = yield* y.promise(() => q(String(a.threadId), r));
362
+ if (o === void 0) return yield* y.fail(new t({ id: i.commentId }));
363
+ yield* M(String(o.anchor), r);
364
+ let s = U(r);
365
+ return String(a.authorSubjectId) !== (s.id ?? "anonymous") && !s.scopes.includes("comments:moderate") ? yield* y.fail(new n({ id: i.commentId })) : (yield* y.promise(async () => {
366
+ for (let e of await T("_voltro_comment_reactions", C("commentId", i.commentId))) await m.delete("_voltro_comment_reactions", String(e.id));
367
+ await m.delete("_voltro_comments", i.commentId);
368
+ }), j(), { ok: !0 });
249
369
  })
250
370
  },
251
371
  {
252
- ...g,
372
+ ...o,
253
373
  description: "Toggle an emoji reaction on a comment.",
254
- execute: (e, n) => _.gen(function* () {
255
- let r = e, i = (yield* _.promise(() => C("_voltro_comments", x("id", r.commentId), 1)))[0];
256
- if (i === void 0) return yield* _.fail(new t({ id: r.commentId }));
257
- let a = yield* _.promise(() => k(String(i.threadId)));
258
- if (a === void 0) return yield* _.fail(new t({ id: r.commentId }));
259
- yield* O(String(a.anchor), n);
260
- let o = B(n).id ?? "anonymous", s = (yield* _.promise(() => C("_voltro_comment_reactions", y(x("commentId", r.commentId), y(x("subjectId", o), x("emoji", r.emoji))), 1)))[0];
261
- if (s !== void 0) return yield* _.promise(() => f.delete("_voltro_comment_reactions", String(s.id))), T(), { reacted: !1 };
262
- let c = S({
374
+ execute: (e, n) => y.gen(function* () {
375
+ let r = e, i = (yield* y.promise(() => T("_voltro_comments", C("id", r.commentId), 1)))[0];
376
+ if (i === void 0) return yield* y.fail(new t({ id: r.commentId }));
377
+ let a = yield* y.promise(() => q(String(i.threadId), n));
378
+ if (a === void 0) return yield* y.fail(new t({ id: r.commentId }));
379
+ yield* M(String(a.anchor), n);
380
+ let o = U(n).id ?? "anonymous", s = (yield* y.promise(() => T("_voltro_comment_reactions", x(C("commentId", r.commentId), x(C("subjectId", o), C("emoji", r.emoji))), 1)))[0];
381
+ if (s !== void 0) return yield* y.promise(() => m.delete("_voltro_comment_reactions", String(s.id))), j(), { reacted: !1 };
382
+ let c = w({
263
383
  kind: "typeid",
264
384
  prefix: "cmtrx"
265
385
  }, "_voltro_comment_reactions");
266
- return yield* _.promise(() => f.insert("_voltro_comment_reactions", {
386
+ return yield* y.promise(() => m.insert("_voltro_comment_reactions", {
267
387
  id: c,
268
388
  commentId: r.commentId,
269
389
  subjectId: o,
270
390
  emoji: r.emoji
271
- })), T(), { reacted: !0 };
391
+ })), j(), { reacted: !0 };
272
392
  })
273
393
  },
274
394
  {
275
- ...i,
395
+ ...u,
276
396
  description: "Stamp the caller's read marker for a thread.",
277
- execute: (e, n) => _.gen(function* () {
397
+ execute: (e, n) => y.gen(function* () {
278
398
  let r = e;
279
- if ((yield* _.promise(() => k(r.threadId))) === void 0) return yield* _.fail(new t({ id: r.threadId }));
280
- let i = B(n).id ?? "anonymous", a = S({
399
+ if ((yield* y.promise(() => W(r.threadId))) === void 0) return yield* y.fail(new t({ id: r.threadId }));
400
+ let i = U(n).id ?? "anonymous", a = w({
281
401
  kind: "typeid",
282
402
  prefix: "cmtread"
283
403
  }, "_voltro_comment_reads");
284
- return yield* _.promise(() => f.upsert("_voltro_comment_reads", {
404
+ return yield* y.promise(() => m.upsert("_voltro_comment_reads", {
285
405
  id: a,
286
406
  threadId: r.threadId,
287
407
  subjectId: i,
@@ -293,50 +413,51 @@ var F = O("_voltro_comment_threads", {
293
413
  })
294
414
  },
295
415
  {
296
- ...c,
416
+ ...v,
297
417
  description: "@-mention autocomplete over the app-declared directory, tenant-filtered.",
298
- execute: (e, t) => _.promise(async () => {
418
+ execute: (e, t) => y.promise(async () => {
299
419
  let { query: n } = e;
300
- if (o.resolveMentions === void 0) return [];
301
- let r = B(t);
302
- return (await o.resolveMentions({
420
+ if (c.resolveMentions === void 0) return [];
421
+ let r = U(t);
422
+ return (await c.resolveMentions({
303
423
  query: n,
304
424
  subject: {
305
425
  id: r.id,
306
426
  tenantId: r.tenantId
307
427
  }
308
- }).catch(() => [])).filter((e) => o.crossTenant === !0 || e.tenantId === void 0 || e.tenantId === null || e.tenantId === r.tenantId).map((e) => ({
428
+ }).catch(() => [])).filter((e) => c.crossTenant === !0 || e.tenantId === void 0 || e.tenantId === null || e.tenantId === r.tenantId).map((e) => ({
309
429
  subjectId: e.subjectId,
310
430
  label: e.label
311
431
  }));
312
432
  })
313
433
  }
314
- ], G = (e) => ({
434
+ ], $ = (e) => ({
315
435
  kind: "json",
316
436
  data: e
317
437
  });
318
- return j({
319
- name: l,
320
- baseName: z,
438
+ return N({
439
+ name: d,
440
+ baseName: H,
321
441
  description: "Comment threads on any app entity — replies, resolve/reopen, @-mentions with notifications, reactions, unread — live over the reactive engine.",
322
442
  permissions: ["inspect:read", "store:write"],
323
443
  extendSchema: { tables: [
324
- F,
325
- I,
326
444
  L,
327
- R
445
+ z,
446
+ R,
447
+ B,
448
+ V
328
449
  ] },
329
- routes: W,
330
- rpcClientDescriptors: p,
450
+ routes: ee,
451
+ rpcClientDescriptors: l,
331
452
  inspectEndpoints: [{
332
453
  method: "GET",
333
454
  path: "/threads",
334
455
  scope: "process",
335
456
  description: "Thread volume and searchable, paginated threads (operator surface).",
336
- handler: (e) => _.promise(async () => {
457
+ handler: (e) => y.promise(async () => {
337
458
  let t = new URL(e.url, "http://inspect.local").searchParams, n = t.get("threadId"), r = Number(t.get("offset") ?? 0), i = Number.isSafeInteger(r) && r >= 0 ? r : 0, a = (t.get("search") ?? "").trim().slice(0, 200);
338
459
  if (n) {
339
- let e = (await C("_voltro_comment_threads", x("id", n), 1))[0], t = x("threadId", n), [r, a] = e && f ? await Promise.all([f.query({
460
+ let e = (await T("_voltro_comment_threads", C("id", n), 1))[0], t = C("threadId", n), [r, a] = e && m ? await Promise.all([m.query({
340
461
  table: "_voltro_comments",
341
462
  predicate: t,
342
463
  order: [{
@@ -349,7 +470,7 @@ var F = O("_voltro_comment_threads", {
349
470
  take: 50,
350
471
  skip: i,
351
472
  projection: void 0
352
- }), f.query({
473
+ }), m.query({
353
474
  table: "_voltro_comments",
354
475
  predicate: t,
355
476
  order: [],
@@ -361,7 +482,7 @@ var F = O("_voltro_comment_threads", {
361
482
  alias: "count"
362
483
  }]
363
484
  })]) : [[], []], o = Number(a[0]?.count ?? 0);
364
- return G({
485
+ return $({
365
486
  stats: {
366
487
  threads: +!!e,
367
488
  open: e && e.status !== "resolved" ? 1 : 0,
@@ -377,15 +498,15 @@ var F = O("_voltro_comment_threads", {
377
498
  id: String(e.id),
378
499
  body: String(e.body),
379
500
  author: String(e.authorSubjectId),
380
- createdAt: A(e.createdAt),
381
- editedAt: e.editedAt == null ? null : A(e.editedAt)
501
+ createdAt: Y(e.createdAt),
502
+ editedAt: e.editedAt == null ? null : Y(e.editedAt)
382
503
  }))
383
504
  }
384
505
  });
385
506
  }
386
- if (!f) throw Error("Comments store is not available");
507
+ if (!m) throw Error("Comments store is not available");
387
508
  let o = async (e, t) => {
388
- let n = await f.query({
509
+ let n = await m.query({
389
510
  table: e,
390
511
  predicate: t,
391
512
  order: [],
@@ -398,8 +519,8 @@ var F = O("_voltro_comment_threads", {
398
519
  }]
399
520
  });
400
521
  return Number(n[0]?.count ?? 0);
401
- }, s = a ? D(b("anchor", a), b("id", a), b("createdBy", a)) : void 0, [c, l, u, d, p] = await Promise.all([
402
- f.query({
522
+ }, s = a ? k(S("anchor", a), S("id", a), S("createdBy", a)) : void 0, [c, l, u, d, f] = await Promise.all([
523
+ m.query({
403
524
  table: "_voltro_comment_threads",
404
525
  predicate: s,
405
526
  order: [{
@@ -414,12 +535,12 @@ var F = O("_voltro_comment_threads", {
414
535
  projection: void 0
415
536
  }),
416
537
  o("_voltro_comment_threads"),
417
- o("_voltro_comment_threads", E("status", "resolved")),
538
+ o("_voltro_comment_threads", O("status", "resolved")),
418
539
  o("_voltro_comments"),
419
540
  o("_voltro_comment_threads", s)
420
- ]), m = c.length === 0 ? [] : await f.query({
541
+ ]), p = c.length === 0 ? [] : await m.query({
421
542
  table: "_voltro_comments",
422
- predicate: w("threadId", c.map((e) => String(e.id))),
543
+ predicate: E("threadId", c.map((e) => String(e.id))),
423
544
  order: [],
424
545
  take: void 0,
425
546
  skip: void 0,
@@ -433,8 +554,8 @@ var F = O("_voltro_comment_threads", {
433
554
  alias: "count"
434
555
  }],
435
556
  groupBy: ["threadId"]
436
- }), h = new Map(m.map((e) => [String(e.threadId), Number(e.count)]));
437
- return G({
557
+ }), h = new Map(p.map((e) => [String(e.threadId), Number(e.count)]));
558
+ return $({
438
559
  stats: {
439
560
  threads: l,
440
561
  open: u,
@@ -443,7 +564,7 @@ var F = O("_voltro_comment_threads", {
443
564
  page: {
444
565
  offset: i,
445
566
  search: a,
446
- total: p,
567
+ total: f,
447
568
  pageSize: 50
448
569
  },
449
570
  recent: c.map((e) => ({
@@ -451,22 +572,66 @@ var F = O("_voltro_comment_threads", {
451
572
  anchor: String(e.anchor),
452
573
  status: String(e.status),
453
574
  createdBy: String(e.createdBy),
454
- createdAt: A(e.createdAt),
575
+ createdAt: Y(e.createdAt),
455
576
  comments: h.get(String(e.id)) ?? 0
456
577
  }))
457
578
  });
458
579
  })
459
580
  }],
460
581
  bindDataStore: (e) => {
461
- f = e, h = e;
582
+ m = e, _ = e;
462
583
  },
463
- onActivate: (e) => _.sync(() => {
464
- o.access?.viaEntity === void 0 && o.access?.scope === void 0 && e.logger.warn("comments: no access rule declared — EVERY read and write will be refused (fail-closed). Set commentsPlugin({ access: { viaEntity } }) or { access: { scope } }."), e.logger.info("comments active", { mentions: o.resolveMentions !== void 0 });
584
+ onActivate: (e) => y.sync(() => {
585
+ c.access?.viaEntity === void 0 && c.access?.scope === void 0 && e.logger.warn("comments: no access rule declared — EVERY read and write will be refused (fail-closed). Set commentsPlugin({ access: { viaEntity } }) or { access: { scope } }."), e.logger.info("comments active", { mentions: c.resolveMentions !== void 0 });
465
586
  }),
466
- onDeactivate: () => _.sync(() => {
467
- f = void 0, h = void 0;
587
+ onDeactivate: () => y.sync(() => {
588
+ m = void 0, _ = void 0;
468
589
  })
469
590
  });
470
- }, H = () => "plugin-comments";
591
+ }, G = async (e, t, n = /* @__PURE__ */ new Date()) => {
592
+ let r = (t, n) => e.query({
593
+ table: t,
594
+ predicate: n,
595
+ order: [],
596
+ take: void 0,
597
+ skip: void 0,
598
+ projection: void 0
599
+ }), i = 0, a = 0;
600
+ for (let [o, s] of Object.entries(t)) {
601
+ if (s === void 0 || !(s >= 0)) continue;
602
+ let t = n.getTime() - s * 864e5;
603
+ for (let c of await r("_voltro_comment_threads", x(C("status", "resolved"), C("kind", o)))) {
604
+ let o = [...await r("_voltro_comment_thread_events", C("threadId", String(c.id)))], l = (e) => new Date(e.at instanceof Date ? e.at : String(e.at)).getTime();
605
+ o.sort((e, t) => l(e) - l(t));
606
+ let u = [...o].reverse().find((e) => e.toStatus === "resolved" && e.fromStatus === "open");
607
+ if (u === void 0 || l(u) > t) continue;
608
+ let d = await r("_voltro_comments", C("threadId", String(c.id)));
609
+ if (d.length !== 0) {
610
+ for (let t of d) {
611
+ for (let n of await r("_voltro_comment_reactions", C("commentId", String(t.id)))) await e.delete("_voltro_comment_reactions", String(n.id));
612
+ await e.delete("_voltro_comments", String(t.id));
613
+ }
614
+ for (let t of await r("_voltro_comment_reads", C("threadId", String(c.id)))) await e.delete("_voltro_comment_reads", String(t.id));
615
+ await e.insert("_voltro_comment_thread_events", {
616
+ id: w({
617
+ kind: "typeid",
618
+ prefix: "cmtevt"
619
+ }, "_voltro_comment_thread_events"),
620
+ threadId: String(c.id),
621
+ fromStatus: "resolved",
622
+ toStatus: "resolved",
623
+ actor: "retention",
624
+ reason: `${d.length} comment(s) removed ${s} day(s) after resolving`,
625
+ at: n.toISOString(),
626
+ tenantId: c.tenantId ?? null
627
+ }), i += 1, a += d.length;
628
+ }
629
+ }
630
+ }
631
+ return {
632
+ threads: i,
633
+ comments: a
634
+ };
635
+ }, K = () => "plugin-comments";
471
636
  //#endregion
472
- export { e as CommentAccessRefused, t as CommentNotFound, n as CommentNotYours, o as QUOTE_MAX_LENGTH, L as commentReactionsTable, R as commentReadsTable, F as commentThreadsTable, u as commentsFeed, V as commentsPlugin, I as commentsTable, f as findQuote, H as pluginComments, l as textQuote };
637
+ export { f as COMMENT_THREAD_KINDS, e as CommentAccessRefused, t as CommentNotFound, n as CommentNotYours, r as CommentStatusRefused, m as QUOTE_MAX_LENGTH, G as applyCommentRetention, B as commentReactionsTable, V as commentReadsTable, z as commentThreadEventsTable, L as commentThreadsTable, h as commentsFeed, W as commentsPlugin, R as commentsTable, d as findQuote, K as pluginComments, c as textQuote };