@voltro/plugin-comments 0.54.0 → 0.56.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,52 +1,52 @@
1
1
  import { CommentAccessRefused as e, CommentNotFound as t, CommentNotYours as n } from "./errors.js";
2
- import { commentsFeed as r, commentsRpcClientImports as i, createDescriptor as a, deleteDescriptor as o, editDescriptor as s, listDescriptor as c, markReadDescriptor as l, mentionSearchDescriptor as u, reactDescriptor as d, resolveDescriptor as f } from "./rpc.js";
3
- import { Effect as p, Option as m } from "effect";
4
- import { and as h, eq as g, generateId as _, id as v, json as y, table as b, text as x, timestamp as S } from "@voltro/database";
5
- import { definePlugin as C, pluginInstanceName as w, publishReactivity as T } from "@voltro/protocol";
6
- import { NotificationService as E } from "@voltro/plugin-notifications";
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
+ 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";
7
7
  //#region src/index.ts
8
- var D = b("_voltro_comment_threads", {
9
- id: v({ prefix: "cmtthr" }),
10
- anchor: x(),
11
- status: x().default("open"),
12
- createdBy: x(),
13
- createdAt: S().default("now"),
14
- tenantId: x().nullable()
15
- }).index("byThreadAnchor", ["anchor", "tenantId"]), O = b("_voltro_comments", {
16
- id: v({ prefix: "cmt" }),
17
- threadId: x(),
18
- body: x(),
19
- authorSubjectId: x(),
20
- mentions: y(),
21
- editedAt: S().nullable(),
22
- createdAt: S().default("now"),
23
- tenantId: x().nullable()
24
- }).index("byCommentThread", ["threadId"]), k = b("_voltro_comment_reactions", {
25
- id: v({ prefix: "cmtrx" }),
26
- commentId: x(),
27
- subjectId: x(),
28
- emoji: x()
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()
29
29
  }).unique("byReactionIdentity", [
30
30
  "commentId",
31
31
  "subjectId",
32
32
  "emoji"
33
- ]).index("byReactionComment", ["commentId"]), A = b("_voltro_comment_reads", {
34
- id: v({ prefix: "cmtread" }),
35
- threadId: x(),
36
- subjectId: x(),
37
- lastReadAt: S()
38
- }).unique("byReadThreadSubject", ["threadId", "subjectId"]), j = "@voltro/plugin-comments", M = (e) => {
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) => {
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
- }, N = (v = {}) => {
46
- let y = w({
47
- base: j,
48
- alias: v.alias,
49
- instance: v.name
45
+ }, P = (r = {}) => {
46
+ let y = T({
47
+ base: M,
48
+ alias: r.alias,
49
+ instance: r.name
50
50
  }), b, x, S = async (e, t, n) => b === void 0 ? [] : b.query({
51
51
  table: e,
52
52
  predicate: t,
@@ -54,40 +54,40 @@ var D = b("_voltro_comment_threads", {
54
54
  take: n,
55
55
  skip: void 0,
56
56
  projection: void 0
57
- }), N = () => {
58
- T(x, r);
59
- }, P = (t, n) => p.gen(function* () {
60
- let r = M(n);
61
- return v.access?.viaEntity === void 0 ? v.access?.scope === void 0 ? yield* p.fail(new e({
57
+ }), C = () => {
58
+ E(x, i);
59
+ }, P = (t, n) => m.gen(function* () {
60
+ let i = N(n);
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 } }"
64
- })) : r.scopes.includes(v.access.scope) ? void 0 : yield* p.fail(new e({
64
+ })) : i.scopes.includes(r.access.scope) ? void 0 : yield* m.fail(new e({
65
65
  anchor: t,
66
- reason: `missing scope '${v.access.scope}'`
67
- })) : (yield* p.promise(async () => {
66
+ reason: `missing scope '${r.access.scope}'`
67
+ })) : (yield* m.promise(async () => {
68
68
  try {
69
- return await v.access.viaEntity({
69
+ return await r.access.viaEntity({
70
70
  anchor: t,
71
- subject: r,
71
+ subject: i,
72
72
  store: b
73
73
  });
74
74
  } catch {
75
75
  return !1;
76
76
  }
77
- })) ? void 0 : yield* p.fail(new e({
77
+ })) ? void 0 : yield* m.fail(new e({
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", g("id", e), 1))[0], I = (e) => e instanceof Date ? e.toISOString() : typeof e == "string" ? e : "", L = (e) => p.gen(function* () {
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* () {
82
82
  if (e.mentions.length === 0) return;
83
- let t = yield* p.serviceOption(E);
84
- if (m.isNone(t)) {
85
- yield* p.logInfo("comments: mention recorded without notification — @voltro/plugin-notifications is not configured");
83
+ let t = yield* m.serviceOption(D);
84
+ if (h.isNone(t)) {
85
+ yield* m.logInfo("comments: mention recorded without notification — @voltro/plugin-notifications is not configured");
86
86
  return;
87
87
  }
88
- for (let n of e.mentions) n !== e.author && (yield* p.promise(() => t.value.send({
88
+ for (let n of e.mentions) n !== e.author && (yield* m.promise(() => t.value.send({
89
89
  to: n,
90
- category: v.mentionCategory ?? "mention",
90
+ category: r.mentionCategory ?? "mention",
91
91
  title: "You were mentioned",
92
92
  body: e.body.length > 200 ? `${e.body.slice(0, 200)}…` : e.body,
93
93
  data: {
@@ -97,24 +97,24 @@ var D = b("_voltro_comment_threads", {
97
97
  tenantId: e.tenantId
98
98
  }).then(() => void 0).catch(() => void 0)));
99
99
  }), R = async (e, t) => {
100
- if (e.length === 0 || v.resolveMentions === void 0) return [];
100
+ if (e.length === 0 || r.resolveMentions === void 0) return [];
101
101
  let n = [];
102
- for (let r of [...new Set(e)].slice(0, 20)) {
103
- let e = (await v.resolveMentions({
104
- query: r,
102
+ for (let i of [...new Set(e)].slice(0, 20)) {
103
+ let e = (await r.resolveMentions({
104
+ query: i,
105
105
  subject: t
106
- }).catch(() => [])).find((e) => e.subjectId === r);
107
- e !== void 0 && (v.crossTenant === !0 || e.tenantId === void 0 || e.tenantId === null || e.tenantId === t.tenantId) && n.push(r);
106
+ }).catch(() => [])).find((e) => e.subjectId === i);
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
110
  }, z = async (e, t, n) => {
111
- let r = await S("_voltro_comment_threads", h(g("anchor", e), g("tenantId", t))), i = [];
111
+ let r = await S("_voltro_comment_threads", g(_("anchor", e), _("tenantId", t))), i = [];
112
112
  for (let e of r) {
113
- let t = [...await S("_voltro_comments", g("threadId", String(e.id)))];
113
+ let t = [...await S("_voltro_comments", _("threadId", String(e.id)))];
114
114
  t.sort((e, t) => I(e.createdAt) < I(t.createdAt) ? -1 : 1);
115
- let r = n === null ? [] : await S("_voltro_comment_reads", h(g("threadId", String(e.id)), g("subjectId", n)), 1), a = r[0] ? I(r[0].lastReadAt) : "", o = [];
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 = [];
116
116
  for (let e of t) {
117
- let t = await S("_voltro_comment_reactions", g("commentId", String(e.id))), r = /* @__PURE__ */ new Map();
117
+ let t = await S("_voltro_comment_reactions", _("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,
@@ -150,31 +150,31 @@ var D = b("_voltro_comment_threads", {
150
150
  return i.sort((e, t) => String(e.createdAt) < String(t.createdAt) ? -1 : 1), i;
151
151
  }, B = "comments:moderate", V = [
152
152
  {
153
- ...c,
153
+ ...l,
154
154
  description: "Threads + comments + reactions + unread for one anchor, live.",
155
- execute: (e, t) => p.gen(function* () {
155
+ execute: (e, t) => m.gen(function* () {
156
156
  let { anchor: n } = e;
157
157
  yield* P(n, t);
158
- let r = M(t);
159
- return yield* p.promise(() => z(n, r.tenantId, r.id));
158
+ let r = N(t);
159
+ return yield* m.promise(() => z(n, r.tenantId, r.id));
160
160
  })
161
161
  },
162
162
  {
163
- ...a,
163
+ ...o,
164
164
  description: "Comment on an anchor — a reply into a thread, or a new thread.",
165
- execute: (e, n) => p.gen(function* () {
165
+ execute: (e, n) => m.gen(function* () {
166
166
  let r = e;
167
167
  yield* P(r.anchor, n);
168
- let i = M(n), a = i.id ?? "anonymous", o = b;
169
- if (o === void 0) return yield* p.fail(new t({ id: r.anchor }));
168
+ let i = N(n), a = i.id ?? "anonymous", o = b;
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* p.promise(() => F(s));
173
- if (e === void 0 || String(e.anchor) !== r.anchor) return yield* p.fail(new t({ id: s }));
174
- } else s = _({
172
+ let e = yield* m.promise(() => F(s));
173
+ if (e === void 0 || String(e.anchor) !== r.anchor) return yield* m.fail(new t({ id: s }));
174
+ } else s = v({
175
175
  kind: "typeid",
176
176
  prefix: "cmtthr"
177
- }, "_voltro_comment_threads"), yield* p.promise(() => o.insert("_voltro_comment_threads", {
177
+ }, "_voltro_comment_threads"), yield* m.promise(() => o.insert("_voltro_comment_threads", {
178
178
  id: s,
179
179
  anchor: r.anchor,
180
180
  status: "open",
@@ -182,11 +182,11 @@ var D = b("_voltro_comment_threads", {
182
182
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
183
183
  tenantId: i.tenantId
184
184
  }));
185
- let c = yield* p.promise(() => R(r.mentions ?? [], i)), l = _({
185
+ let c = yield* m.promise(() => R(r.mentions ?? [], i)), l = v({
186
186
  kind: "typeid",
187
187
  prefix: "cmt"
188
188
  }, "_voltro_comments");
189
- return yield* p.promise(() => o.insert("_voltro_comments", {
189
+ return yield* m.promise(() => o.insert("_voltro_comments", {
190
190
  id: l,
191
191
  threadId: s,
192
192
  body: r.body,
@@ -195,7 +195,7 @@ var D = b("_voltro_comment_threads", {
195
195
  editedAt: null,
196
196
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
197
197
  tenantId: i.tenantId
198
- })), N(), yield* L({
198
+ })), C(), yield* L({
199
199
  mentions: c,
200
200
  author: a,
201
201
  anchor: r.anchor,
@@ -209,76 +209,76 @@ var D = b("_voltro_comment_threads", {
209
209
  })
210
210
  },
211
211
  {
212
- ...s,
212
+ ...c,
213
213
  description: "Edit your own comment.",
214
- execute: (e, r) => p.gen(function* () {
215
- let i = e, a = (yield* p.promise(() => S("_voltro_comments", g("id", i.commentId), 1)))[0];
216
- if (a === void 0) return yield* p.fail(new t({ id: i.commentId }));
217
- let o = yield* p.promise(() => F(String(a.threadId)));
218
- return o === void 0 ? yield* p.fail(new t({ id: i.commentId })) : (yield* P(String(o.anchor), r), String(a.authorSubjectId) === (M(r).id ?? "anonymous") ? (yield* p.promise(() => b.update("_voltro_comments", i.commentId, {
214
+ execute: (e, r) => m.gen(function* () {
215
+ let i = e, a = (yield* m.promise(() => S("_voltro_comments", _("id", i.commentId), 1)))[0];
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, {
219
219
  body: i.body,
220
220
  editedAt: (/* @__PURE__ */ new Date()).toISOString()
221
- })), N(), { ok: !0 }) : yield* p.fail(new n({ id: i.commentId })));
221
+ })), C(), { ok: !0 }) : yield* m.fail(new n({ id: i.commentId })));
222
222
  })
223
223
  },
224
224
  {
225
- ...f,
225
+ ...p,
226
226
  description: "Resolve or reopen a thread.",
227
- execute: (e, n) => p.gen(function* () {
228
- let r = e, i = yield* p.promise(() => F(r.threadId));
229
- return i === void 0 ? yield* p.fail(new t({ id: r.threadId })) : (yield* P(String(i.anchor), n), yield* p.promise(() => b.update("_voltro_comment_threads", r.threadId, { status: r.resolved ? "resolved" : "open" })), N(), { ok: !0 });
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 });
230
230
  })
231
231
  },
232
232
  {
233
- ...o,
233
+ ...s,
234
234
  description: "Delete your own comment (comments:moderate deletes any).",
235
- execute: (e, r) => p.gen(function* () {
236
- let i = e, a = (yield* p.promise(() => S("_voltro_comments", g("id", i.commentId), 1)))[0];
237
- if (a === void 0) return yield* p.fail(new t({ id: i.commentId }));
238
- let o = yield* p.promise(() => F(String(a.threadId)));
239
- if (o === void 0) return yield* p.fail(new t({ id: i.commentId }));
235
+ execute: (e, r) => m.gen(function* () {
236
+ let i = e, a = (yield* m.promise(() => S("_voltro_comments", _("id", i.commentId), 1)))[0];
237
+ if (a === void 0) return yield* m.fail(new t({ id: i.commentId }));
238
+ let o = yield* m.promise(() => F(String(a.threadId)));
239
+ if (o === void 0) return yield* m.fail(new t({ id: i.commentId }));
240
240
  yield* P(String(o.anchor), r);
241
- let s = M(r);
242
- return String(a.authorSubjectId) !== (s.id ?? "anonymous") && !s.scopes.includes(B) ? yield* p.fail(new n({ id: i.commentId })) : (yield* p.promise(async () => {
243
- for (let e of await S("_voltro_comment_reactions", g("commentId", i.commentId))) await b.delete("_voltro_comment_reactions", String(e.id));
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
244
  await b.delete("_voltro_comments", i.commentId);
245
- }), N(), { ok: !0 });
245
+ }), C(), { ok: !0 });
246
246
  })
247
247
  },
248
248
  {
249
- ...d,
249
+ ...f,
250
250
  description: "Toggle an emoji reaction on a comment.",
251
- execute: (e, n) => p.gen(function* () {
252
- let r = e, i = (yield* p.promise(() => S("_voltro_comments", g("id", r.commentId), 1)))[0];
253
- if (i === void 0) return yield* p.fail(new t({ id: r.commentId }));
254
- let a = yield* p.promise(() => F(String(i.threadId)));
255
- if (a === void 0) return yield* p.fail(new t({ id: r.commentId }));
251
+ execute: (e, n) => m.gen(function* () {
252
+ let r = e, i = (yield* m.promise(() => S("_voltro_comments", _("id", r.commentId), 1)))[0];
253
+ if (i === void 0) return yield* m.fail(new t({ id: r.commentId }));
254
+ let a = yield* m.promise(() => F(String(i.threadId)));
255
+ if (a === void 0) return yield* m.fail(new t({ id: r.commentId }));
256
256
  yield* P(String(a.anchor), n);
257
- let o = M(n).id ?? "anonymous", s = (yield* p.promise(() => S("_voltro_comment_reactions", h(g("commentId", r.commentId), h(g("subjectId", o), g("emoji", r.emoji))), 1)))[0];
258
- if (s !== void 0) return yield* p.promise(() => b.delete("_voltro_comment_reactions", String(s.id))), N(), { reacted: !1 };
259
- let c = _({
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({
260
260
  kind: "typeid",
261
261
  prefix: "cmtrx"
262
262
  }, "_voltro_comment_reactions");
263
- return yield* p.promise(() => b.insert("_voltro_comment_reactions", {
263
+ return yield* m.promise(() => b.insert("_voltro_comment_reactions", {
264
264
  id: c,
265
265
  commentId: r.commentId,
266
266
  subjectId: o,
267
267
  emoji: r.emoji
268
- })), N(), { reacted: !0 };
268
+ })), C(), { reacted: !0 };
269
269
  })
270
270
  },
271
271
  {
272
- ...l,
272
+ ...u,
273
273
  description: "Stamp the caller's read marker for a thread.",
274
- execute: (e, n) => p.gen(function* () {
274
+ execute: (e, n) => m.gen(function* () {
275
275
  let r = e;
276
- if ((yield* p.promise(() => F(r.threadId))) === void 0) return yield* p.fail(new t({ id: r.threadId }));
277
- let i = M(n).id ?? "anonymous", a = _({
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({
278
278
  kind: "typeid",
279
279
  prefix: "cmtread"
280
280
  }, "_voltro_comment_reads");
281
- return yield* p.promise(() => b.upsert("_voltro_comment_reads", {
281
+ return yield* m.promise(() => b.upsert("_voltro_comment_reads", {
282
282
  id: a,
283
283
  threadId: r.threadId,
284
284
  subjectId: i,
@@ -290,19 +290,19 @@ var D = b("_voltro_comment_threads", {
290
290
  })
291
291
  },
292
292
  {
293
- ...u,
293
+ ...d,
294
294
  description: "@-mention autocomplete over the app-declared directory, tenant-filtered.",
295
- execute: (e, t) => p.promise(async () => {
295
+ execute: (e, t) => m.promise(async () => {
296
296
  let { query: n } = e;
297
- if (v.resolveMentions === void 0) return [];
298
- let r = M(t);
299
- return (await v.resolveMentions({
297
+ if (r.resolveMentions === void 0) return [];
298
+ let i = N(t);
299
+ return (await r.resolveMentions({
300
300
  query: n,
301
301
  subject: {
302
- id: r.id,
303
- tenantId: r.tenantId
302
+ id: i.id,
303
+ tenantId: i.tenantId
304
304
  }
305
- }).catch(() => [])).filter((e) => v.crossTenant === !0 || e.tenantId === void 0 || e.tenantId === null || e.tenantId === r.tenantId).map((e) => ({
305
+ }).catch(() => [])).filter((e) => r.crossTenant === !0 || e.tenantId === void 0 || e.tenantId === null || e.tenantId === i.tenantId).map((e) => ({
306
306
  subjectId: e.subjectId,
307
307
  label: e.label
308
308
  }));
@@ -312,25 +312,25 @@ var D = b("_voltro_comment_threads", {
312
312
  kind: "json",
313
313
  data: e
314
314
  });
315
- return C({
315
+ return w({
316
316
  name: y,
317
- baseName: j,
317
+ baseName: M,
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
320
  declaredScopes: [B],
321
321
  extendSchema: { tables: [
322
- D,
323
322
  O,
324
323
  k,
325
- A
324
+ A,
325
+ j
326
326
  ] },
327
327
  routes: V,
328
- rpcClientDescriptors: i,
328
+ rpcClientDescriptors: a,
329
329
  inspectEndpoints: [{
330
330
  method: "GET",
331
331
  path: "/threads",
332
332
  description: "Thread volume + the most recent threads (operator surface).",
333
- handler: () => p.promise(async () => {
333
+ handler: () => m.promise(async () => {
334
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
335
  id: String(e.id),
336
336
  anchor: String(e.anchor),
@@ -352,13 +352,13 @@ var D = b("_voltro_comment_threads", {
352
352
  bindDataStore: (e) => {
353
353
  b = e, x = e;
354
354
  },
355
- onActivate: (e) => p.sync(() => {
356
- v.access?.viaEntity === void 0 && v.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: v.resolveMentions !== void 0 });
355
+ onActivate: (e) => m.sync(() => {
356
+ 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
357
  }),
358
- onDeactivate: () => p.sync(() => {
358
+ onDeactivate: () => m.sync(() => {
359
359
  b = void 0, x = void 0;
360
360
  })
361
361
  });
362
- }, P = () => "plugin-comments";
362
+ }, F = () => "plugin-comments";
363
363
  //#endregion
364
- export { e as CommentAccessRefused, t as CommentNotFound, n as CommentNotYours, k as commentReactionsTable, A as commentReadsTable, D as commentThreadsTable, r as commentsFeed, N as commentsPlugin, O as commentsTable, P as pluginComments };
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 };
package/dist/rpc.d.ts CHANGED
@@ -4,6 +4,18 @@ import { QueryProcedureDescriptor } from '@voltro/protocol';
4
4
  import { ReactivityChannel } from '@voltro/protocol';
5
5
  import { Schema } from 'effect';
6
6
 
7
+ /**
8
+ * The plugin's canonical package name — the key an app's `alias` is recorded
9
+ * under, and what `./web`'s hooks resolve their tags through.
10
+ *
11
+ * It lives in this BROWSER-SAFE module rather than in `index.ts` because both
12
+ * halves need it and only one of them may reach the server graph: `index.ts`
13
+ * passes it to `pluginInstanceName` / `baseName`, and `web.ts` passes it to
14
+ * `pluginTag`. A second copy in `web.ts` would be a second definition of the
15
+ * plugin's identity with nothing comparing them.
16
+ */
17
+ export declare const BASE_NAME = "@voltro/plugin-comments";
18
+
7
19
  /** The anchor's access check refused the caller — or no access rule is
8
20
  * declared at all (fail-closed: undeclared access is a refusal, not a pass).
9
21
  * Also the answer for a thread whose anchor row no longer resolves (a
package/dist/rpc.js CHANGED
@@ -2,7 +2,7 @@ import { CommentAccessRefused as e, CommentNotFound as t, CommentNotYours as n }
2
2
  import { Schema as r } from "effect";
3
3
  import { defineMutation as i, defineQuery as a, reactivityChannel as o } from "@voltro/protocol";
4
4
  //#region src/rpc.ts
5
- var s = o("comments"), c = r.Struct({
5
+ var s = "@voltro/plugin-comments", c = o("comments"), l = r.Struct({
6
6
  id: r.String,
7
7
  threadId: r.String,
8
8
  body: r.String,
@@ -15,22 +15,22 @@ var s = o("comments"), c = r.Struct({
15
15
  count: r.Number,
16
16
  mine: r.Boolean
17
17
  }))
18
- }), l = r.Struct({
18
+ }), u = r.Struct({
19
19
  id: r.String,
20
20
  anchor: r.String,
21
21
  status: r.Literal("open", "resolved"),
22
22
  createdBy: r.String,
23
23
  createdAt: r.String,
24
- comments: r.Array(c),
24
+ comments: r.Array(l),
25
25
  unreadCount: r.Number
26
- }), u = a({
26
+ }), d = a({
27
27
  name: "comments.list",
28
- source: s,
28
+ source: c,
29
29
  input: r.Struct({ anchor: r.String }),
30
- output: r.Array(l),
30
+ output: r.Array(u),
31
31
  error: e,
32
32
  openAccess: "delegated: the executor runs the app-declared anchor access rule (viaEntity guard or scope) and is fail-closed — an undeclared rule refuses every read"
33
- }), d = i({
33
+ }), f = i({
34
34
  name: "comments.create",
35
35
  input: r.Struct({
36
36
  anchor: r.String,
@@ -44,7 +44,7 @@ var s = o("comments"), c = r.Struct({
44
44
  }),
45
45
  error: r.Union(e, t),
46
46
  openAccess: "delegated: the executor runs the app-declared anchor access rule before any write; the author is always the calling subject"
47
- }), f = i({
47
+ }), p = i({
48
48
  name: "comments.edit",
49
49
  input: r.Struct({
50
50
  commentId: r.String,
@@ -53,7 +53,7 @@ var s = o("comments"), c = r.Struct({
53
53
  output: r.Struct({ ok: r.Boolean }),
54
54
  error: r.Union(e, t, n),
55
55
  openAccess: "author-scoped: only the comment's own author may edit, checked in the executor on top of the anchor access rule"
56
- }), p = i({
56
+ }), m = i({
57
57
  name: "comments.resolve",
58
58
  input: r.Struct({
59
59
  threadId: r.String,
@@ -62,13 +62,13 @@ var s = o("comments"), c = r.Struct({
62
62
  output: r.Struct({ ok: r.Boolean }),
63
63
  error: r.Union(e, t),
64
64
  openAccess: "delegated: anchor access rule in the executor — anyone who may read the anchor may resolve/reopen (the Liveblocks semantic)"
65
- }), m = i({
65
+ }), h = i({
66
66
  name: "comments.delete",
67
67
  input: r.Struct({ commentId: r.String }),
68
68
  output: r.Struct({ ok: r.Boolean }),
69
69
  error: r.Union(e, t, n),
70
70
  openAccess: "author-scoped, with a comments:moderate scope override — both checked in the executor on top of the anchor access rule"
71
- }), h = i({
71
+ }), g = i({
72
72
  name: "comments.react",
73
73
  input: r.Struct({
74
74
  commentId: r.String,
@@ -77,13 +77,13 @@ var s = o("comments"), c = r.Struct({
77
77
  output: r.Struct({ reacted: r.Boolean }),
78
78
  error: r.Union(e, t),
79
79
  openAccess: "delegated: anchor access rule in the executor; the toggle writes only the calling subject's own reaction row"
80
- }), g = i({
80
+ }), _ = i({
81
81
  name: "comments.markRead",
82
82
  input: r.Struct({ threadId: r.String }),
83
83
  output: r.Struct({ ok: r.Boolean }),
84
84
  error: t,
85
85
  openAccess: "self-scoped write: stamps only the calling subject's own read marker for the thread"
86
- }), _ = a({
86
+ }), v = a({
87
87
  name: "comments.mentionSearch",
88
88
  input: r.Struct({ query: r.String }),
89
89
  output: r.Array(r.Struct({
@@ -91,7 +91,7 @@ var s = o("comments"), c = r.Struct({
91
91
  label: r.String
92
92
  })),
93
93
  openAccess: "delegated: the app-declared mention resolver receives the CALLING subject and its default filters to the caller's tenant — cross-tenant subjects are structurally unreachable"
94
- }), v = [
94
+ }), y = [
95
95
  {
96
96
  tag: "comments.list",
97
97
  kind: "query",
@@ -158,4 +158,4 @@ var s = o("comments"), c = r.Struct({
158
158
  }
159
159
  ];
160
160
  //#endregion
161
- export { s as commentsFeed, v as commentsRpcClientImports, d as createDescriptor, m as deleteDescriptor, f as editDescriptor, u as listDescriptor, g as markReadDescriptor, _ as mentionSearchDescriptor, h as reactDescriptor, p as resolveDescriptor };
161
+ export { s as BASE_NAME, c as commentsFeed, y as commentsRpcClientImports, f as createDescriptor, h as deleteDescriptor, p as editDescriptor, d as listDescriptor, _ as markReadDescriptor, v as mentionSearchDescriptor, g as reactDescriptor, m as resolveDescriptor };
package/dist/web.js CHANGED
@@ -1,35 +1,37 @@
1
- import { useMutation as e, useSubscription as t } from "@voltro/client";
1
+ import { BASE_NAME as e } from "./rpc.js";
2
+ import { pluginTag as t } from "@voltro/protocol";
3
+ import { useMutation as n, useSubscription as r } from "@voltro/client";
2
4
  //#region src/web.ts
3
- var n = (n, r = "app") => {
4
- let { data: i, loading: a } = t(r, "comments.list", { anchor: n }), o = e(r, "comments.create"), s = e(r, "comments.edit"), c = e(r, "comments.resolve"), l = e(r, "comments.delete"), u = e(r, "comments.react"), d = e(r, "comments.markRead"), f = i ?? [];
5
+ var i = (n) => t(e, n), a = (e, t = "app") => {
6
+ let { data: a, loading: o } = r(t, i("list"), { anchor: e }), s = n(t, i("create")), c = n(t, i("edit")), l = n(t, i("resolve")), u = n(t, i("delete")), d = n(t, i("react")), f = n(t, i("markRead")), p = a ?? [];
5
7
  return {
6
- threads: f,
7
- pending: a,
8
- unreadCount: f.reduce((e, t) => e + t.unreadCount, 0),
9
- create: (e, t) => o.mutate({
10
- anchor: n,
11
- body: e,
12
- ...t?.threadId === void 0 ? {} : { threadId: t.threadId },
13
- ...t?.mentions === void 0 ? {} : { mentions: t.mentions }
8
+ threads: p,
9
+ pending: o,
10
+ unreadCount: p.reduce((e, t) => e + t.unreadCount, 0),
11
+ create: (t, n) => s.mutate({
12
+ anchor: e,
13
+ body: t,
14
+ ...n?.threadId === void 0 ? {} : { threadId: n.threadId },
15
+ ...n?.mentions === void 0 ? {} : { mentions: n.mentions }
14
16
  }),
15
- edit: (e, t) => s.mutate({
17
+ edit: (e, t) => c.mutate({
16
18
  commentId: e,
17
19
  body: t
18
20
  }),
19
- resolve: (e, t = !0) => c.mutate({
21
+ resolve: (e, t = !0) => l.mutate({
20
22
  threadId: e,
21
23
  resolved: t
22
24
  }),
23
- remove: (e) => l.mutate({ commentId: e }),
24
- react: (e, t) => u.mutate({
25
+ remove: (e) => u.mutate({ commentId: e }),
26
+ react: (e, t) => d.mutate({
25
27
  commentId: e,
26
28
  emoji: t
27
29
  }),
28
- markRead: (e) => d.mutate({ threadId: e })
30
+ markRead: (e) => f.mutate({ threadId: e })
29
31
  };
30
- }, r = (e, t, r = "app") => n(e, r).threads.find((e) => e.id === t), i = (e, n = "app") => {
31
- let { data: r } = t(n, "comments.mentionSearch", { query: e });
32
- return r ?? [];
32
+ }, o = (e, t, n = "app") => a(e, n).threads.find((e) => e.id === t), s = (e, t = "app") => {
33
+ let { data: n } = r(t, i("mentionSearch"), { query: e });
34
+ return n ?? [];
33
35
  };
34
36
  //#endregion
35
- export { n as useComments, i as useMentionSearch, r as useThread };
37
+ export { a as useComments, s as useMentionSearch, o as useThread };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voltro/plugin-comments",
3
- "version": "0.54.0",
3
+ "version": "0.56.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.54.0",
52
- "@voltro/protocol": "0.54.0",
53
- "@voltro/plugin-notifications": "0.54.0",
51
+ "@voltro/database": "0.56.0",
52
+ "@voltro/protocol": "0.56.0",
53
+ "@voltro/plugin-notifications": "0.56.0",
54
54
  "effect": "^3.22.0"
55
55
  },
56
56
  "peerDependencies": {