@voltro/plugin-comments 0.81.0 → 0.83.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.d.ts CHANGED
@@ -57,6 +57,15 @@ declare const CommentNotYours_base: Schema.TaggedErrorClass<CommentNotYours, "Co
57
57
  id: typeof Schema.String;
58
58
  }>;
59
59
 
60
+ export declare interface CommentQuote {
61
+ /** The selected text. */
62
+ readonly exact: string;
63
+ /** Up to 32 characters just before it, to tell repeated passages apart. */
64
+ readonly prefix?: string;
65
+ /** Up to 32 characters just after it. */
66
+ readonly suffix?: string;
67
+ }
68
+
60
69
  export declare const commentReactionsTable: Table<"_voltro_comment_reactions", FieldDefinitions<{
61
70
  readonly id: ColumnBuilder<string, "id", boolean>;
62
71
  readonly commentId: ColumnBuilder<string, "text", boolean>;
@@ -137,12 +146,25 @@ export declare const commentThreadsTable: Table<"_voltro_comment_threads", Field
137
146
  /** Free string key (`order:123`, `doc:abc#section-2`) — the app decides the
138
147
  * vocabulary; the access rule receives it verbatim. */
139
148
  readonly anchor: ColumnBuilder<string, "text", boolean>;
149
+ /** The selected passage the thread is about (`./quote`), or null. */
150
+ readonly quote: ColumnBuilder<CommentQuote | null, "json", boolean>;
140
151
  readonly status: ColumnBuilder<string, "text", true>;
141
152
  readonly createdBy: ColumnBuilder<string, "text", boolean>;
142
153
  readonly createdAt: ColumnBuilder<Date, "timestamp", true>;
143
154
  readonly tenantId: ColumnBuilder<string | null, "text", boolean>;
144
155
  }>, true, "byThreadAnchor">;
145
156
 
157
+ /**
158
+ * Where `quote` stands in `text` now: `{ start, end }`, or null when the text
159
+ * no longer holds the passage. With the passage more than once, the one whose
160
+ * surroundings match the quote's prefix and suffix best wins (the first on a
161
+ * tie).
162
+ */
163
+ export declare const findQuote: (text: string, quote: CommentQuote) => {
164
+ readonly start: number;
165
+ readonly end: number;
166
+ } | null;
167
+
146
168
  export declare interface MentionSearchInput {
147
169
  readonly query: string;
148
170
  /** The CALLING subject — REQUIRED in the seam signature so no implementation
@@ -158,4 +180,10 @@ export declare interface MentionSearchInput {
158
180
  * is the API. */
159
181
  export declare const pluginComments: () => string;
160
182
 
183
+ /** The longest quoted passage a thread keeps. */
184
+ export declare const QUOTE_MAX_LENGTH = 1000;
185
+
186
+ /** The quote for `text` between `start` and `end` (a text control's selection). */
187
+ export declare const textQuote: (text: string, start: number, end: number) => CommentQuote;
188
+
161
189
  export { }
package/dist/index.js CHANGED
@@ -1,93 +1,94 @@
1
1
  import { CommentAccessRefused as e, CommentNotFound as t, CommentNotYours as n } from "./errors.js";
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, 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";
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";
7
7
  //#region src/index.ts
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()
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()
29
30
  }).unique("byReactionIdentity", [
30
31
  "commentId",
31
32
  "subjectId",
32
33
  "emoji"
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) => {
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) => {
39
40
  let t = e.request.subject ?? {};
40
41
  return {
41
42
  id: t.id ?? null,
42
43
  tenantId: t.tenantId ?? null,
43
44
  scopes: t.scopes ?? []
44
45
  };
45
- }, R = (r = {}) => {
46
- let b = k({
47
- base: I,
48
- alias: r.alias,
49
- instance: r.name
50
- }), S, T, E = async (e, t, n) => S === void 0 ? [] : S.query({
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({
51
52
  table: e,
52
53
  predicate: t,
53
54
  order: [],
54
55
  take: n,
55
56
  skip: void 0,
56
57
  projection: void 0
57
- }), D = () => {
58
- A(T, i);
59
- }, R = (t, n) => m.gen(function* () {
60
- let i = L(n);
61
- return r.access?.viaEntity === void 0 ? r.access?.scope === void 0 ? yield* m.fail(new e({
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({
62
63
  anchor: t,
63
64
  reason: "no access rule declared — set commentsPlugin({ access: { viaEntity } }) or { access: { scope } }"
64
- })) : i.scopes.includes(r.access.scope) ? void 0 : yield* m.fail(new e({
65
+ })) : r.scopes.includes(o.access.scope) ? void 0 : yield* _.fail(new e({
65
66
  anchor: t,
66
- reason: `missing scope '${r.access.scope}'`
67
- })) : (yield* m.promise(async () => {
67
+ reason: `missing scope '${o.access.scope}'`
68
+ })) : (yield* _.promise(async () => {
68
69
  try {
69
- return await r.access.viaEntity({
70
+ return await o.access.viaEntity({
70
71
  anchor: t,
71
- subject: i,
72
- store: S
72
+ subject: r,
73
+ store: f
73
74
  });
74
75
  } catch {
75
76
  return !1;
76
77
  }
77
- })) ? void 0 : yield* m.fail(new e({
78
+ })) ? void 0 : yield* _.fail(new e({
78
79
  anchor: t,
79
80
  reason: "the anchored entity is not readable by this caller (or no longer available)"
80
81
  }));
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
+ }), 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* () {
82
83
  if (e.mentions.length === 0) return;
83
- let t = yield* m.serviceOption(j);
84
- if (h.isNone(t)) {
85
- yield* m.logInfo("comments: mention recorded without notification — @voltro/plugin-notifications is not configured");
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");
86
87
  return;
87
88
  }
88
- for (let n of e.mentions) n !== e.author && (yield* m.promise(() => t.value.send({
89
+ for (let n of e.mentions) n !== e.author && (yield* _.promise(() => t.value.send({
89
90
  to: n,
90
- category: r.mentionCategory ?? "mention",
91
+ category: o.mentionCategory ?? "mention",
91
92
  title: "You were mentioned",
92
93
  body: e.body.length > 200 ? `${e.body.slice(0, 200)}…` : e.body,
93
94
  data: {
@@ -97,24 +98,24 @@ var M = T("_voltro_comment_threads", {
97
98
  tenantId: e.tenantId
98
99
  }).then(() => void 0).catch(() => void 0)));
99
100
  }), H = async (e, t) => {
100
- if (e.length === 0 || r.resolveMentions === void 0) return [];
101
+ if (e.length === 0 || o.resolveMentions === void 0) return [];
101
102
  let n = [];
102
- for (let i of [...new Set(e)].slice(0, 20)) {
103
- let e = (await r.resolveMentions({
104
- query: i,
103
+ for (let r of [...new Set(e)].slice(0, 20)) {
104
+ let e = (await o.resolveMentions({
105
+ query: r,
105
106
  subject: t
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);
107
+ }).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);
108
109
  }
109
110
  return n;
110
111
  }, U = async (e, t, n) => {
111
- let r = await E("_voltro_comment_threads", g(v("anchor", e), v("tenantId", t))), i = [];
112
+ let r = await C("_voltro_comment_threads", y(x("anchor", e), x("tenantId", t))), i = [];
112
113
  for (let e of r) {
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 = [];
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 = [];
116
117
  for (let e of t) {
117
- let t = await E("_voltro_comment_reactions", v("commentId", String(e.id))), r = /* @__PURE__ */ new Map();
118
+ let t = await C("_voltro_comment_reactions", x("commentId", String(e.id))), r = /* @__PURE__ */ new Map();
118
119
  for (let e of t) {
119
120
  let t = r.get(String(e.emoji)) ?? {
120
121
  count: 0,
@@ -128,8 +129,8 @@ var M = T("_voltro_comment_threads", {
128
129
  body: String(e.body),
129
130
  authorSubjectId: String(e.authorSubjectId),
130
131
  mentions: e.mentions ?? [],
131
- editedAt: e.editedAt == null ? null : B(e.editedAt),
132
- createdAt: B(e.createdAt),
132
+ editedAt: e.editedAt == null ? null : A(e.editedAt),
133
+ createdAt: A(e.createdAt),
133
134
  reactions: [...r.entries()].map(([e, t]) => ({
134
135
  emoji: e,
135
136
  count: t.count,
@@ -140,53 +141,55 @@ var M = T("_voltro_comment_threads", {
140
141
  i.push({
141
142
  id: String(e.id),
142
143
  anchor: String(e.anchor),
144
+ quote: e.quote ?? null,
143
145
  status: e.status === "resolved" ? "resolved" : "open",
144
146
  createdBy: String(e.createdBy),
145
- createdAt: B(e.createdAt),
147
+ createdAt: A(e.createdAt),
146
148
  comments: o,
147
149
  unreadCount: o.filter((e) => String(e.authorSubjectId) !== n && String(e.createdAt) > a).length
148
150
  });
149
151
  }
150
152
  return i.sort((e, t) => String(e.createdAt) < String(t.createdAt) ? -1 : 1), i;
151
- }, W = "comments:moderate", G = [
153
+ }, W = [
152
154
  {
153
- ...l,
155
+ ...m,
154
156
  description: "Threads + comments + reactions + unread for one anchor, live.",
155
- execute: (e, t) => m.gen(function* () {
157
+ execute: (e, t) => _.gen(function* () {
156
158
  let { anchor: n } = e;
157
- yield* R(n, t);
158
- let r = L(t);
159
- return yield* m.promise(() => U(n, r.tenantId, r.id));
159
+ yield* O(n, t);
160
+ let r = B(t);
161
+ return yield* _.promise(() => U(n, r.tenantId, r.id));
160
162
  })
161
163
  },
162
164
  {
163
- ...o,
165
+ ...s,
164
166
  description: "Comment on an anchor — a reply into a thread, or a new thread.",
165
- execute: (e, n) => m.gen(function* () {
167
+ execute: (e, n) => _.gen(function* () {
166
168
  let r = e;
167
- yield* R(r.anchor, n);
168
- let i = L(n), a = i.id ?? "anonymous", o = S;
169
- if (o === void 0) return yield* m.fail(new t({ id: r.anchor }));
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 }));
170
172
  let s = r.threadId;
171
173
  if (s !== void 0) {
172
- let e = yield* m.promise(() => z(s));
173
- if (e === void 0 || String(e.anchor) !== r.anchor) return yield* m.fail(new t({ id: s }));
174
- } else s = y({
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({
175
177
  kind: "typeid",
176
178
  prefix: "cmtthr"
177
- }, "_voltro_comment_threads"), yield* m.promise(() => o.insert("_voltro_comment_threads", {
179
+ }, "_voltro_comment_threads"), yield* _.promise(() => o.insert("_voltro_comment_threads", {
178
180
  id: s,
179
181
  anchor: r.anchor,
182
+ quote: r.quote ?? null,
180
183
  status: "open",
181
184
  createdBy: a,
182
185
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
183
186
  tenantId: i.tenantId
184
187
  }));
185
- let c = yield* m.promise(() => H(r.mentions ?? [], i)), l = y({
188
+ let c = yield* _.promise(() => H(r.mentions ?? [], i)), l = S({
186
189
  kind: "typeid",
187
190
  prefix: "cmt"
188
191
  }, "_voltro_comments");
189
- return yield* m.promise(() => o.insert("_voltro_comments", {
192
+ return yield* _.promise(() => o.insert("_voltro_comments", {
190
193
  id: l,
191
194
  threadId: s,
192
195
  body: r.body,
@@ -195,7 +198,7 @@ var M = T("_voltro_comment_threads", {
195
198
  editedAt: null,
196
199
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
197
200
  tenantId: i.tenantId
198
- })), D(), yield* V({
201
+ })), T(), yield* V({
199
202
  mentions: c,
200
203
  author: a,
201
204
  anchor: r.anchor,
@@ -209,76 +212,76 @@ var M = T("_voltro_comment_threads", {
209
212
  })
210
213
  },
211
214
  {
212
- ...c,
215
+ ...d,
213
216
  description: "Edit your own comment.",
214
- execute: (e, r) => m.gen(function* () {
215
- let i = e, a = (yield* m.promise(() => E("_voltro_comments", v("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(() => 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, {
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, {
219
222
  body: i.body,
220
223
  editedAt: (/* @__PURE__ */ new Date()).toISOString()
221
- })), D(), { ok: !0 }) : yield* m.fail(new n({ id: i.commentId })));
224
+ })), T(), { ok: !0 }) : yield* _.fail(new n({ id: i.commentId })));
222
225
  })
223
226
  },
224
227
  {
225
- ...p,
228
+ ...a,
226
229
  description: "Resolve or reopen a thread.",
227
- execute: (e, n) => m.gen(function* () {
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
+ 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 });
230
233
  })
231
234
  },
232
235
  {
233
- ...s,
236
+ ...r,
234
237
  description: "Delete your own comment (comments:moderate deletes any).",
235
- execute: (e, r) => m.gen(function* () {
236
- let i = e, a = (yield* m.promise(() => E("_voltro_comments", v("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(() => z(String(a.threadId)));
239
- if (o === void 0) return yield* m.fail(new t({ id: i.commentId }));
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 });
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 });
246
249
  })
247
250
  },
248
251
  {
249
- ...f,
252
+ ...g,
250
253
  description: "Toggle an emoji reaction on a comment.",
251
- execute: (e, n) => m.gen(function* () {
252
- let r = e, i = (yield* m.promise(() => E("_voltro_comments", v("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(() => z(String(i.threadId)));
255
- if (a === void 0) return yield* m.fail(new t({ id: r.commentId }));
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({
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({
260
263
  kind: "typeid",
261
264
  prefix: "cmtrx"
262
265
  }, "_voltro_comment_reactions");
263
- return yield* m.promise(() => S.insert("_voltro_comment_reactions", {
266
+ return yield* _.promise(() => f.insert("_voltro_comment_reactions", {
264
267
  id: c,
265
268
  commentId: r.commentId,
266
269
  subjectId: o,
267
270
  emoji: r.emoji
268
- })), D(), { reacted: !0 };
271
+ })), T(), { reacted: !0 };
269
272
  })
270
273
  },
271
274
  {
272
- ...u,
275
+ ...i,
273
276
  description: "Stamp the caller's read marker for a thread.",
274
- execute: (e, n) => m.gen(function* () {
277
+ execute: (e, n) => _.gen(function* () {
275
278
  let r = e;
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({
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({
278
281
  kind: "typeid",
279
282
  prefix: "cmtread"
280
283
  }, "_voltro_comment_reads");
281
- return yield* m.promise(() => S.upsert("_voltro_comment_reads", {
284
+ return yield* _.promise(() => f.upsert("_voltro_comment_reads", {
282
285
  id: a,
283
286
  threadId: r.threadId,
284
287
  subjectId: i,
@@ -290,51 +293,50 @@ var M = T("_voltro_comment_threads", {
290
293
  })
291
294
  },
292
295
  {
293
- ...d,
296
+ ...c,
294
297
  description: "@-mention autocomplete over the app-declared directory, tenant-filtered.",
295
- execute: (e, t) => m.promise(async () => {
298
+ execute: (e, t) => _.promise(async () => {
296
299
  let { query: n } = e;
297
- if (r.resolveMentions === void 0) return [];
298
- let i = L(t);
299
- return (await r.resolveMentions({
300
+ if (o.resolveMentions === void 0) return [];
301
+ let r = B(t);
302
+ return (await o.resolveMentions({
300
303
  query: n,
301
304
  subject: {
302
- id: i.id,
303
- tenantId: i.tenantId
305
+ id: r.id,
306
+ tenantId: r.tenantId
304
307
  }
305
- }).catch(() => [])).filter((e) => r.crossTenant === !0 || e.tenantId === void 0 || e.tenantId === null || e.tenantId === i.tenantId).map((e) => ({
308
+ }).catch(() => [])).filter((e) => o.crossTenant === !0 || e.tenantId === void 0 || e.tenantId === null || e.tenantId === r.tenantId).map((e) => ({
306
309
  subjectId: e.subjectId,
307
310
  label: e.label
308
311
  }));
309
312
  })
310
313
  }
311
- ], K = (e) => ({
314
+ ], G = (e) => ({
312
315
  kind: "json",
313
316
  data: e
314
317
  });
315
- return O({
316
- name: b,
317
- baseName: I,
318
+ return j({
319
+ name: l,
320
+ baseName: z,
318
321
  description: "Comment threads on any app entity — replies, resolve/reopen, @-mentions with notifications, reactions, unread — live over the reactive engine.",
319
322
  permissions: ["inspect:read", "store:write"],
320
- declaredScopes: [W],
321
323
  extendSchema: { tables: [
322
- M,
323
- N,
324
- P,
325
- F
324
+ F,
325
+ I,
326
+ L,
327
+ R
326
328
  ] },
327
- routes: G,
328
- rpcClientDescriptors: a,
329
+ routes: W,
330
+ rpcClientDescriptors: p,
329
331
  inspectEndpoints: [{
330
332
  method: "GET",
331
333
  path: "/threads",
332
334
  scope: "process",
333
335
  description: "Thread volume and searchable, paginated threads (operator surface).",
334
- handler: (e) => m.promise(async () => {
336
+ handler: (e) => _.promise(async () => {
335
337
  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);
336
338
  if (n) {
337
- 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({
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({
338
340
  table: "_voltro_comments",
339
341
  predicate: t,
340
342
  order: [{
@@ -347,7 +349,7 @@ var M = T("_voltro_comment_threads", {
347
349
  take: 50,
348
350
  skip: i,
349
351
  projection: void 0
350
- }), S.query({
352
+ }), f.query({
351
353
  table: "_voltro_comments",
352
354
  predicate: t,
353
355
  order: [],
@@ -359,7 +361,7 @@ var M = T("_voltro_comment_threads", {
359
361
  alias: "count"
360
362
  }]
361
363
  })]) : [[], []], o = Number(a[0]?.count ?? 0);
362
- return K({
364
+ return G({
363
365
  stats: {
364
366
  threads: +!!e,
365
367
  open: e && e.status !== "resolved" ? 1 : 0,
@@ -375,15 +377,15 @@ var M = T("_voltro_comment_threads", {
375
377
  id: String(e.id),
376
378
  body: String(e.body),
377
379
  author: String(e.authorSubjectId),
378
- createdAt: B(e.createdAt),
379
- editedAt: e.editedAt == null ? null : B(e.editedAt)
380
+ createdAt: A(e.createdAt),
381
+ editedAt: e.editedAt == null ? null : A(e.editedAt)
380
382
  }))
381
383
  }
382
384
  });
383
385
  }
384
- if (!S) throw Error("Comments store is not available");
386
+ if (!f) throw Error("Comments store is not available");
385
387
  let o = async (e, t) => {
386
- let n = await S.query({
388
+ let n = await f.query({
387
389
  table: e,
388
390
  predicate: t,
389
391
  order: [],
@@ -396,8 +398,8 @@ var M = T("_voltro_comment_threads", {
396
398
  }]
397
399
  });
398
400
  return Number(n[0]?.count ?? 0);
399
- }, s = a ? w(_("anchor", a), _("id", a), _("createdBy", a)) : void 0, [c, l, u, d, f] = await Promise.all([
400
- S.query({
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({
401
403
  table: "_voltro_comment_threads",
402
404
  predicate: s,
403
405
  order: [{
@@ -412,12 +414,12 @@ var M = T("_voltro_comment_threads", {
412
414
  projection: void 0
413
415
  }),
414
416
  o("_voltro_comment_threads"),
415
- o("_voltro_comment_threads", C("status", "resolved")),
417
+ o("_voltro_comment_threads", E("status", "resolved")),
416
418
  o("_voltro_comments"),
417
419
  o("_voltro_comment_threads", s)
418
- ]), p = c.length === 0 ? [] : await S.query({
420
+ ]), m = c.length === 0 ? [] : await f.query({
419
421
  table: "_voltro_comments",
420
- predicate: x("threadId", c.map((e) => String(e.id))),
422
+ predicate: w("threadId", c.map((e) => String(e.id))),
421
423
  order: [],
422
424
  take: void 0,
423
425
  skip: void 0,
@@ -431,8 +433,8 @@ var M = T("_voltro_comment_threads", {
431
433
  alias: "count"
432
434
  }],
433
435
  groupBy: ["threadId"]
434
- }), m = new Map(p.map((e) => [String(e.threadId), Number(e.count)]));
435
- return K({
436
+ }), h = new Map(m.map((e) => [String(e.threadId), Number(e.count)]));
437
+ return G({
436
438
  stats: {
437
439
  threads: l,
438
440
  open: u,
@@ -441,7 +443,7 @@ var M = T("_voltro_comment_threads", {
441
443
  page: {
442
444
  offset: i,
443
445
  search: a,
444
- total: f,
446
+ total: p,
445
447
  pageSize: 50
446
448
  },
447
449
  recent: c.map((e) => ({
@@ -449,22 +451,22 @@ var M = T("_voltro_comment_threads", {
449
451
  anchor: String(e.anchor),
450
452
  status: String(e.status),
451
453
  createdBy: String(e.createdBy),
452
- createdAt: B(e.createdAt),
453
- comments: m.get(String(e.id)) ?? 0
454
+ createdAt: A(e.createdAt),
455
+ comments: h.get(String(e.id)) ?? 0
454
456
  }))
455
457
  });
456
458
  })
457
459
  }],
458
460
  bindDataStore: (e) => {
459
- S = e, T = e;
461
+ f = e, h = e;
460
462
  },
461
- onActivate: (e) => m.sync(() => {
462
- 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 });
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 });
463
465
  }),
464
- onDeactivate: () => m.sync(() => {
465
- S = void 0, T = void 0;
466
+ onDeactivate: () => _.sync(() => {
467
+ f = void 0, h = void 0;
466
468
  })
467
469
  });
468
- }, z = () => "plugin-comments";
470
+ }, H = () => "plugin-comments";
469
471
  //#endregion
470
- 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 };
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 };