@voltro/plugin-comments 0.69.1 → 0.71.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 (3) hide show
  1. package/CHANGELOG.md +1369 -111
  2. package/dist/index.js +221 -116
  3. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -1,63 +1,63 @@
1
1
  import { CommentAccessRefused as e, CommentNotFound as t, CommentNotYours as n } from "./errors.js";
2
2
  import { BASE_NAME as r, commentsFeed as i, commentsRpcClientImports as a, createDescriptor as o, deleteDescriptor as s, editDescriptor as c, listDescriptor as l, markReadDescriptor as u, mentionSearchDescriptor as d, reactDescriptor as f, resolveDescriptor as p } from "./rpc.js";
3
3
  import { Effect as m, Option as h } from "effect";
4
- import { and as g, eq as _, generateId as v, id as y, json as b, table as x, text as S, timestamp as C } from "@voltro/database";
5
- import { definePlugin as w, pluginInstanceName as T, publishReactivity as E } from "@voltro/protocol";
6
- import { NotificationService as D } from "@voltro/plugin-notifications";
4
+ import { and as g, contains as _, eq as v, generateId as y, id as b, inSet as x, json as S, neq as C, or as w, table as T, text as E, timestamp as D } from "@voltro/database";
5
+ import { definePlugin as O, pluginInstanceName as k, publishReactivity as A } from "@voltro/protocol";
6
+ import { NotificationService as j } from "@voltro/plugin-notifications";
7
7
  //#region src/index.ts
8
- var O = x("_voltro_comment_threads", {
9
- id: y({ prefix: "cmtthr" }),
10
- anchor: S(),
11
- status: S().default("open"),
12
- createdBy: S(),
13
- createdAt: C().default("now"),
14
- tenantId: S().nullable()
15
- }).index("byThreadAnchor", ["anchor", "tenantId"]), k = x("_voltro_comments", {
16
- id: y({ prefix: "cmt" }),
17
- threadId: S(),
18
- body: S(),
19
- authorSubjectId: S(),
20
- mentions: b(),
21
- editedAt: C().nullable(),
22
- createdAt: C().default("now"),
23
- tenantId: S().nullable()
24
- }).index("byCommentThread", ["threadId"]), A = x("_voltro_comment_reactions", {
25
- id: y({ prefix: "cmtrx" }),
26
- commentId: S(),
27
- subjectId: S(),
28
- emoji: S()
8
+ var M = T("_voltro_comment_threads", {
9
+ id: b({ prefix: "cmtthr" }),
10
+ anchor: E(),
11
+ status: E().default("open"),
12
+ createdBy: E(),
13
+ createdAt: D().default("now"),
14
+ tenantId: E().nullable()
15
+ }).index("byThreadAnchor", ["anchor", "tenantId"]), N = T("_voltro_comments", {
16
+ id: b({ prefix: "cmt" }),
17
+ threadId: E(),
18
+ body: E(),
19
+ authorSubjectId: E(),
20
+ mentions: S(),
21
+ editedAt: D().nullable(),
22
+ createdAt: D().default("now"),
23
+ tenantId: E().nullable()
24
+ }).index("byCommentThread", ["threadId"]), P = T("_voltro_comment_reactions", {
25
+ id: b({ prefix: "cmtrx" }),
26
+ commentId: E(),
27
+ subjectId: E(),
28
+ emoji: E()
29
29
  }).unique("byReactionIdentity", [
30
30
  "commentId",
31
31
  "subjectId",
32
32
  "emoji"
33
- ]).index("byReactionComment", ["commentId"]), j = x("_voltro_comment_reads", {
34
- id: y({ prefix: "cmtread" }),
35
- threadId: S(),
36
- subjectId: S(),
37
- lastReadAt: C()
38
- }).unique("byReadThreadSubject", ["threadId", "subjectId"]), M = r, N = (e) => {
33
+ ]).index("byReactionComment", ["commentId"]), F = T("_voltro_comment_reads", {
34
+ id: b({ prefix: "cmtread" }),
35
+ threadId: E(),
36
+ subjectId: E(),
37
+ lastReadAt: D()
38
+ }).unique("byReadThreadSubject", ["threadId", "subjectId"]), I = r, L = (e) => {
39
39
  let t = e.request.subject ?? {};
40
40
  return {
41
41
  id: t.id ?? null,
42
42
  tenantId: t.tenantId ?? null,
43
43
  scopes: t.scopes ?? []
44
44
  };
45
- }, P = (r = {}) => {
46
- let y = T({
47
- base: M,
45
+ }, R = (r = {}) => {
46
+ let b = k({
47
+ base: I,
48
48
  alias: r.alias,
49
49
  instance: r.name
50
- }), b, x, S = async (e, t, n) => b === void 0 ? [] : b.query({
50
+ }), S, T, E = async (e, t, n) => S === void 0 ? [] : S.query({
51
51
  table: e,
52
52
  predicate: t,
53
53
  order: [],
54
54
  take: n,
55
55
  skip: void 0,
56
56
  projection: void 0
57
- }), C = () => {
58
- E(x, i);
59
- }, P = (t, n) => m.gen(function* () {
60
- let i = N(n);
57
+ }), D = () => {
58
+ A(T, i);
59
+ }, R = (t, n) => m.gen(function* () {
60
+ let i = L(n);
61
61
  return r.access?.viaEntity === void 0 ? r.access?.scope === void 0 ? yield* m.fail(new e({
62
62
  anchor: t,
63
63
  reason: "no access rule declared — set commentsPlugin({ access: { viaEntity } }) or { access: { scope } }"
@@ -69,7 +69,7 @@ var O = x("_voltro_comment_threads", {
69
69
  return await r.access.viaEntity({
70
70
  anchor: t,
71
71
  subject: i,
72
- store: b
72
+ store: S
73
73
  });
74
74
  } catch {
75
75
  return !1;
@@ -78,9 +78,9 @@ var O = x("_voltro_comment_threads", {
78
78
  anchor: t,
79
79
  reason: "the anchored entity is not readable by this caller (or no longer available)"
80
80
  }));
81
- }), F = async (e) => (await S("_voltro_comment_threads", _("id", e), 1))[0], I = (e) => e instanceof Date ? e.toISOString() : typeof e == "string" ? e : "", L = (e) => m.gen(function* () {
81
+ }), z = async (e) => (await E("_voltro_comment_threads", v("id", e), 1))[0], B = (e) => e instanceof Date ? e.toISOString() : typeof e == "string" ? e : "", V = (e) => m.gen(function* () {
82
82
  if (e.mentions.length === 0) return;
83
- let t = yield* m.serviceOption(D);
83
+ let t = yield* m.serviceOption(j);
84
84
  if (h.isNone(t)) {
85
85
  yield* m.logInfo("comments: mention recorded without notification — @voltro/plugin-notifications is not configured");
86
86
  return;
@@ -96,7 +96,7 @@ var O = x("_voltro_comment_threads", {
96
96
  },
97
97
  tenantId: e.tenantId
98
98
  }).then(() => void 0).catch(() => void 0)));
99
- }), R = async (e, t) => {
99
+ }), H = async (e, t) => {
100
100
  if (e.length === 0 || r.resolveMentions === void 0) return [];
101
101
  let n = [];
102
102
  for (let i of [...new Set(e)].slice(0, 20)) {
@@ -107,14 +107,14 @@ var O = x("_voltro_comment_threads", {
107
107
  e !== void 0 && (r.crossTenant === !0 || e.tenantId === void 0 || e.tenantId === null || e.tenantId === t.tenantId) && n.push(i);
108
108
  }
109
109
  return n;
110
- }, z = async (e, t, n) => {
111
- let r = await S("_voltro_comment_threads", g(_("anchor", e), _("tenantId", t))), i = [];
110
+ }, U = async (e, t, n) => {
111
+ let r = await E("_voltro_comment_threads", g(v("anchor", e), v("tenantId", t))), i = [];
112
112
  for (let e of r) {
113
- let t = [...await S("_voltro_comments", _("threadId", String(e.id)))];
114
- t.sort((e, t) => I(e.createdAt) < I(t.createdAt) ? -1 : 1);
115
- let r = n === null ? [] : await S("_voltro_comment_reads", g(_("threadId", String(e.id)), _("subjectId", n)), 1), a = r[0] ? I(r[0].lastReadAt) : "", o = [];
113
+ let t = [...await E("_voltro_comments", v("threadId", String(e.id)))];
114
+ t.sort((e, t) => B(e.createdAt) < B(t.createdAt) ? -1 : 1);
115
+ let r = n === null ? [] : await E("_voltro_comment_reads", g(v("threadId", String(e.id)), v("subjectId", n)), 1), a = r[0] ? B(r[0].lastReadAt) : "", o = [];
116
116
  for (let e of t) {
117
- let t = await S("_voltro_comment_reactions", _("commentId", String(e.id))), r = /* @__PURE__ */ new Map();
117
+ let t = await E("_voltro_comment_reactions", v("commentId", String(e.id))), r = /* @__PURE__ */ new Map();
118
118
  for (let e of t) {
119
119
  let t = r.get(String(e.emoji)) ?? {
120
120
  count: 0,
@@ -128,8 +128,8 @@ var O = x("_voltro_comment_threads", {
128
128
  body: String(e.body),
129
129
  authorSubjectId: String(e.authorSubjectId),
130
130
  mentions: e.mentions ?? [],
131
- editedAt: e.editedAt == null ? null : I(e.editedAt),
132
- createdAt: I(e.createdAt),
131
+ editedAt: e.editedAt == null ? null : B(e.editedAt),
132
+ createdAt: B(e.createdAt),
133
133
  reactions: [...r.entries()].map(([e, t]) => ({
134
134
  emoji: e,
135
135
  count: t.count,
@@ -142,21 +142,21 @@ var O = x("_voltro_comment_threads", {
142
142
  anchor: String(e.anchor),
143
143
  status: e.status === "resolved" ? "resolved" : "open",
144
144
  createdBy: String(e.createdBy),
145
- createdAt: I(e.createdAt),
145
+ createdAt: B(e.createdAt),
146
146
  comments: o,
147
147
  unreadCount: o.filter((e) => String(e.authorSubjectId) !== n && String(e.createdAt) > a).length
148
148
  });
149
149
  }
150
150
  return i.sort((e, t) => String(e.createdAt) < String(t.createdAt) ? -1 : 1), i;
151
- }, B = "comments:moderate", V = [
151
+ }, W = "comments:moderate", G = [
152
152
  {
153
153
  ...l,
154
154
  description: "Threads + comments + reactions + unread for one anchor, live.",
155
155
  execute: (e, t) => m.gen(function* () {
156
156
  let { anchor: n } = e;
157
- yield* P(n, t);
158
- let r = N(t);
159
- return yield* m.promise(() => z(n, r.tenantId, r.id));
157
+ yield* R(n, t);
158
+ let r = L(t);
159
+ return yield* m.promise(() => U(n, r.tenantId, r.id));
160
160
  })
161
161
  },
162
162
  {
@@ -164,14 +164,14 @@ var O = x("_voltro_comment_threads", {
164
164
  description: "Comment on an anchor — a reply into a thread, or a new thread.",
165
165
  execute: (e, n) => m.gen(function* () {
166
166
  let r = e;
167
- yield* P(r.anchor, n);
168
- let i = N(n), a = i.id ?? "anonymous", o = b;
167
+ yield* R(r.anchor, n);
168
+ let i = L(n), a = i.id ?? "anonymous", o = S;
169
169
  if (o === void 0) return yield* m.fail(new t({ id: r.anchor }));
170
170
  let s = r.threadId;
171
171
  if (s !== void 0) {
172
- let e = yield* m.promise(() => F(s));
172
+ let e = yield* m.promise(() => z(s));
173
173
  if (e === void 0 || String(e.anchor) !== r.anchor) return yield* m.fail(new t({ id: s }));
174
- } else s = v({
174
+ } else s = y({
175
175
  kind: "typeid",
176
176
  prefix: "cmtthr"
177
177
  }, "_voltro_comment_threads"), yield* m.promise(() => o.insert("_voltro_comment_threads", {
@@ -182,7 +182,7 @@ var O = x("_voltro_comment_threads", {
182
182
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
183
183
  tenantId: i.tenantId
184
184
  }));
185
- let c = yield* m.promise(() => R(r.mentions ?? [], i)), l = v({
185
+ let c = yield* m.promise(() => H(r.mentions ?? [], i)), l = y({
186
186
  kind: "typeid",
187
187
  prefix: "cmt"
188
188
  }, "_voltro_comments");
@@ -195,7 +195,7 @@ var O = x("_voltro_comment_threads", {
195
195
  editedAt: null,
196
196
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
197
197
  tenantId: i.tenantId
198
- })), C(), yield* L({
198
+ })), D(), yield* V({
199
199
  mentions: c,
200
200
  author: a,
201
201
  anchor: r.anchor,
@@ -212,60 +212,60 @@ var O = x("_voltro_comment_threads", {
212
212
  ...c,
213
213
  description: "Edit your own comment.",
214
214
  execute: (e, r) => m.gen(function* () {
215
- let i = e, a = (yield* m.promise(() => S("_voltro_comments", _("id", i.commentId), 1)))[0];
215
+ let i = e, a = (yield* m.promise(() => E("_voltro_comments", v("id", i.commentId), 1)))[0];
216
216
  if (a === void 0) return yield* m.fail(new t({ id: i.commentId }));
217
- let o = yield* m.promise(() => F(String(a.threadId)));
218
- return o === void 0 ? yield* m.fail(new t({ id: i.commentId })) : (yield* P(String(o.anchor), r), String(a.authorSubjectId) === (N(r).id ?? "anonymous") ? (yield* m.promise(() => b.update("_voltro_comments", i.commentId, {
217
+ let o = yield* m.promise(() => z(String(a.threadId)));
218
+ return o === void 0 ? yield* m.fail(new t({ id: i.commentId })) : (yield* R(String(o.anchor), r), String(a.authorSubjectId) === (L(r).id ?? "anonymous") ? (yield* m.promise(() => S.update("_voltro_comments", i.commentId, {
219
219
  body: i.body,
220
220
  editedAt: (/* @__PURE__ */ new Date()).toISOString()
221
- })), C(), { ok: !0 }) : yield* m.fail(new n({ id: i.commentId })));
221
+ })), D(), { ok: !0 }) : yield* m.fail(new n({ id: i.commentId })));
222
222
  })
223
223
  },
224
224
  {
225
225
  ...p,
226
226
  description: "Resolve or reopen a thread.",
227
227
  execute: (e, n) => m.gen(function* () {
228
- let r = e, i = yield* m.promise(() => F(r.threadId));
229
- return i === void 0 ? yield* m.fail(new t({ id: r.threadId })) : (yield* P(String(i.anchor), n), yield* m.promise(() => b.update("_voltro_comment_threads", r.threadId, { status: r.resolved ? "resolved" : "open" })), C(), { ok: !0 });
228
+ let r = e, i = yield* m.promise(() => z(r.threadId));
229
+ return i === void 0 ? yield* m.fail(new t({ id: r.threadId })) : (yield* R(String(i.anchor), n), yield* m.promise(() => S.update("_voltro_comment_threads", r.threadId, { status: r.resolved ? "resolved" : "open" })), D(), { ok: !0 });
230
230
  })
231
231
  },
232
232
  {
233
233
  ...s,
234
234
  description: "Delete your own comment (comments:moderate deletes any).",
235
235
  execute: (e, r) => m.gen(function* () {
236
- let i = e, a = (yield* m.promise(() => S("_voltro_comments", _("id", i.commentId), 1)))[0];
236
+ let i = e, a = (yield* m.promise(() => E("_voltro_comments", v("id", i.commentId), 1)))[0];
237
237
  if (a === void 0) return yield* m.fail(new t({ id: i.commentId }));
238
- let o = yield* m.promise(() => F(String(a.threadId)));
238
+ let o = yield* m.promise(() => z(String(a.threadId)));
239
239
  if (o === void 0) return yield* m.fail(new t({ id: i.commentId }));
240
- yield* P(String(o.anchor), r);
241
- let s = N(r);
242
- return String(a.authorSubjectId) !== (s.id ?? "anonymous") && !s.scopes.includes(B) ? yield* m.fail(new n({ id: i.commentId })) : (yield* m.promise(async () => {
243
- for (let e of await S("_voltro_comment_reactions", _("commentId", i.commentId))) await b.delete("_voltro_comment_reactions", String(e.id));
244
- await b.delete("_voltro_comments", i.commentId);
245
- }), C(), { ok: !0 });
240
+ yield* R(String(o.anchor), r);
241
+ let s = L(r);
242
+ return String(a.authorSubjectId) !== (s.id ?? "anonymous") && !s.scopes.includes(W) ? yield* m.fail(new n({ id: i.commentId })) : (yield* m.promise(async () => {
243
+ for (let e of await E("_voltro_comment_reactions", v("commentId", i.commentId))) await S.delete("_voltro_comment_reactions", String(e.id));
244
+ await S.delete("_voltro_comments", i.commentId);
245
+ }), D(), { ok: !0 });
246
246
  })
247
247
  },
248
248
  {
249
249
  ...f,
250
250
  description: "Toggle an emoji reaction on a comment.",
251
251
  execute: (e, n) => m.gen(function* () {
252
- let r = e, i = (yield* m.promise(() => S("_voltro_comments", _("id", r.commentId), 1)))[0];
252
+ let r = e, i = (yield* m.promise(() => E("_voltro_comments", v("id", r.commentId), 1)))[0];
253
253
  if (i === void 0) return yield* m.fail(new t({ id: r.commentId }));
254
- let a = yield* m.promise(() => F(String(i.threadId)));
254
+ let a = yield* m.promise(() => z(String(i.threadId)));
255
255
  if (a === void 0) return yield* m.fail(new t({ id: r.commentId }));
256
- yield* P(String(a.anchor), n);
257
- let o = N(n).id ?? "anonymous", s = (yield* m.promise(() => S("_voltro_comment_reactions", g(_("commentId", r.commentId), g(_("subjectId", o), _("emoji", r.emoji))), 1)))[0];
258
- if (s !== void 0) return yield* m.promise(() => b.delete("_voltro_comment_reactions", String(s.id))), C(), { reacted: !1 };
259
- let c = v({
256
+ yield* R(String(a.anchor), n);
257
+ let o = L(n).id ?? "anonymous", s = (yield* m.promise(() => E("_voltro_comment_reactions", g(v("commentId", r.commentId), g(v("subjectId", o), v("emoji", r.emoji))), 1)))[0];
258
+ if (s !== void 0) return yield* m.promise(() => S.delete("_voltro_comment_reactions", String(s.id))), D(), { reacted: !1 };
259
+ let c = y({
260
260
  kind: "typeid",
261
261
  prefix: "cmtrx"
262
262
  }, "_voltro_comment_reactions");
263
- return yield* m.promise(() => b.insert("_voltro_comment_reactions", {
263
+ return yield* m.promise(() => S.insert("_voltro_comment_reactions", {
264
264
  id: c,
265
265
  commentId: r.commentId,
266
266
  subjectId: o,
267
267
  emoji: r.emoji
268
- })), C(), { reacted: !0 };
268
+ })), D(), { reacted: !0 };
269
269
  })
270
270
  },
271
271
  {
@@ -273,12 +273,12 @@ var O = x("_voltro_comment_threads", {
273
273
  description: "Stamp the caller's read marker for a thread.",
274
274
  execute: (e, n) => m.gen(function* () {
275
275
  let r = e;
276
- if ((yield* m.promise(() => F(r.threadId))) === void 0) return yield* m.fail(new t({ id: r.threadId }));
277
- let i = N(n).id ?? "anonymous", a = v({
276
+ if ((yield* m.promise(() => z(r.threadId))) === void 0) return yield* m.fail(new t({ id: r.threadId }));
277
+ let i = L(n).id ?? "anonymous", a = y({
278
278
  kind: "typeid",
279
279
  prefix: "cmtread"
280
280
  }, "_voltro_comment_reads");
281
- return yield* m.promise(() => b.upsert("_voltro_comment_reads", {
281
+ return yield* m.promise(() => S.upsert("_voltro_comment_reads", {
282
282
  id: a,
283
283
  threadId: r.threadId,
284
284
  subjectId: i,
@@ -295,7 +295,7 @@ var O = x("_voltro_comment_threads", {
295
295
  execute: (e, t) => m.promise(async () => {
296
296
  let { query: n } = e;
297
297
  if (r.resolveMentions === void 0) return [];
298
- let i = N(t);
298
+ let i = L(t);
299
299
  return (await r.resolveMentions({
300
300
  query: n,
301
301
  subject: {
@@ -308,57 +308,162 @@ var O = x("_voltro_comment_threads", {
308
308
  }));
309
309
  })
310
310
  }
311
- ], H = (e) => ({
311
+ ], K = (e) => ({
312
312
  kind: "json",
313
313
  data: e
314
314
  });
315
- return w({
316
- name: y,
317
- baseName: M,
315
+ return O({
316
+ name: b,
317
+ baseName: I,
318
318
  description: "Comment threads on any app entity — replies, resolve/reopen, @-mentions with notifications, reactions, unread — live over the reactive engine.",
319
319
  permissions: ["inspect:read", "store:write"],
320
- declaredScopes: [B],
320
+ declaredScopes: [W],
321
321
  extendSchema: { tables: [
322
- O,
323
- k,
324
- A,
325
- j
322
+ M,
323
+ N,
324
+ P,
325
+ F
326
326
  ] },
327
- routes: V,
327
+ routes: G,
328
328
  rpcClientDescriptors: a,
329
329
  inspectEndpoints: [{
330
330
  method: "GET",
331
331
  path: "/threads",
332
- description: "Thread volume + the most recent threads (operator surface).",
333
- handler: () => m.promise(async () => {
334
- let e = await S("_voltro_comment_threads", void 0), t = await S("_voltro_comments", void 0), n = [...e].sort((e, t) => I(e.createdAt) > I(t.createdAt) ? -1 : 1).slice(0, 50).map((e) => ({
335
- id: String(e.id),
336
- anchor: String(e.anchor),
337
- status: String(e.status),
338
- createdBy: String(e.createdBy),
339
- createdAt: I(e.createdAt),
340
- comments: t.filter((t) => String(t.threadId) === String(e.id)).length
341
- }));
342
- return H({
332
+ description: "Thread volume and searchable, paginated threads (operator surface).",
333
+ handler: (e) => m.promise(async () => {
334
+ 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);
335
+ if (n) {
336
+ let e = (await E("_voltro_comment_threads", v("id", n), 1))[0], t = v("threadId", n), [r, a] = e && S ? await Promise.all([S.query({
337
+ table: "_voltro_comments",
338
+ predicate: t,
339
+ order: [{
340
+ column: "createdAt",
341
+ direction: "asc"
342
+ }, {
343
+ column: "id",
344
+ direction: "asc"
345
+ }],
346
+ take: 50,
347
+ skip: i,
348
+ projection: void 0
349
+ }), S.query({
350
+ table: "_voltro_comments",
351
+ predicate: t,
352
+ order: [],
353
+ take: void 0,
354
+ skip: void 0,
355
+ projection: void 0,
356
+ aggregations: [{
357
+ op: "count",
358
+ alias: "count"
359
+ }]
360
+ })]) : [[], []], o = Number(a[0]?.count ?? 0);
361
+ return K({
362
+ stats: {
363
+ threads: +!!e,
364
+ open: e && e.status !== "resolved" ? 1 : 0,
365
+ comments: o
366
+ },
367
+ recent: [],
368
+ detail: {
369
+ threadId: n,
370
+ found: !!e,
371
+ offset: i,
372
+ total: o,
373
+ messages: r.map((e) => ({
374
+ id: String(e.id),
375
+ body: String(e.body),
376
+ author: String(e.authorSubjectId),
377
+ createdAt: B(e.createdAt),
378
+ editedAt: e.editedAt == null ? null : B(e.editedAt)
379
+ }))
380
+ }
381
+ });
382
+ }
383
+ if (!S) throw Error("Comments store is not available");
384
+ let o = async (e, t) => {
385
+ let n = await S.query({
386
+ table: e,
387
+ predicate: t,
388
+ order: [],
389
+ take: void 0,
390
+ skip: void 0,
391
+ projection: void 0,
392
+ aggregations: [{
393
+ op: "count",
394
+ alias: "count"
395
+ }]
396
+ });
397
+ return Number(n[0]?.count ?? 0);
398
+ }, s = a ? w(_("anchor", a), _("id", a), _("createdBy", a)) : void 0, [c, l, u, d, f] = await Promise.all([
399
+ S.query({
400
+ table: "_voltro_comment_threads",
401
+ predicate: s,
402
+ order: [{
403
+ column: "createdAt",
404
+ direction: "desc"
405
+ }, {
406
+ column: "id",
407
+ direction: "desc"
408
+ }],
409
+ take: 50,
410
+ skip: i,
411
+ projection: void 0
412
+ }),
413
+ o("_voltro_comment_threads"),
414
+ o("_voltro_comment_threads", C("status", "resolved")),
415
+ o("_voltro_comments"),
416
+ o("_voltro_comment_threads", s)
417
+ ]), p = c.length === 0 ? [] : await S.query({
418
+ table: "_voltro_comments",
419
+ predicate: x("threadId", c.map((e) => String(e.id))),
420
+ order: [],
421
+ take: void 0,
422
+ skip: void 0,
423
+ projection: void 0,
424
+ aggregations: [{
425
+ op: "column",
426
+ column: "threadId",
427
+ alias: "threadId"
428
+ }, {
429
+ op: "count",
430
+ alias: "count"
431
+ }],
432
+ groupBy: ["threadId"]
433
+ }), m = new Map(p.map((e) => [String(e.threadId), Number(e.count)]));
434
+ return K({
343
435
  stats: {
344
- threads: e.length,
345
- open: e.filter((e) => e.status !== "resolved").length,
346
- comments: t.length
436
+ threads: l,
437
+ open: u,
438
+ comments: d
347
439
  },
348
- recent: n
440
+ page: {
441
+ offset: i,
442
+ search: a,
443
+ total: f,
444
+ pageSize: 50
445
+ },
446
+ recent: c.map((e) => ({
447
+ id: String(e.id),
448
+ anchor: String(e.anchor),
449
+ status: String(e.status),
450
+ createdBy: String(e.createdBy),
451
+ createdAt: B(e.createdAt),
452
+ comments: m.get(String(e.id)) ?? 0
453
+ }))
349
454
  });
350
455
  })
351
456
  }],
352
457
  bindDataStore: (e) => {
353
- b = e, x = e;
458
+ S = e, T = e;
354
459
  },
355
460
  onActivate: (e) => m.sync(() => {
356
461
  r.access?.viaEntity === void 0 && r.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: r.resolveMentions !== void 0 });
357
462
  }),
358
463
  onDeactivate: () => m.sync(() => {
359
- b = void 0, x = void 0;
464
+ S = void 0, T = void 0;
360
465
  })
361
466
  });
362
- }, F = () => "plugin-comments";
467
+ }, z = () => "plugin-comments";
363
468
  //#endregion
364
- export { e as CommentAccessRefused, t as CommentNotFound, n as CommentNotYours, A as commentReactionsTable, j as commentReadsTable, O as commentThreadsTable, i as commentsFeed, P as commentsPlugin, k as commentsTable, F as pluginComments };
469
+ export { e as CommentAccessRefused, t as CommentNotFound, n as CommentNotYours, P as commentReactionsTable, F as commentReadsTable, M as commentThreadsTable, i as commentsFeed, R as commentsPlugin, N as commentsTable, z as pluginComments };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voltro/plugin-comments",
3
- "version": "0.69.1",
3
+ "version": "0.71.0",
4
4
  "description": "Comment threads on any anchor your app can name — replies, resolve/reopen, reactions, per-subject unread — live over the existing reactivity channel, with no second push mechanism. Access delegates to your app's own guard (access.viaEntity) or a scope and fails closed when neither is declared; @-mentions are re-filtered to the caller's tenant on search and re-validated at create, then delivered through plugin-notifications when it is installed. useComments / useThread / useMentionSearch in /web, an ejectable <CommentsThread> in @voltro/ui, plus inspect endpoints and a dashboard panel.",
5
5
  "keywords": [
6
6
  "voltro",
@@ -48,9 +48,9 @@
48
48
  "node": ">=24.0.0"
49
49
  },
50
50
  "dependencies": {
51
- "@voltro/database": "0.69.1",
52
- "@voltro/plugin-notifications": "0.69.1",
53
- "@voltro/protocol": "0.69.1"
51
+ "@voltro/database": "0.71.0",
52
+ "@voltro/plugin-notifications": "0.71.0",
53
+ "@voltro/protocol": "0.71.0"
54
54
  },
55
55
  "peerDependencies": {
56
56
  "effect": "^3.22.1",