@voltro/plugin-comments 0.81.0 → 0.82.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.
@@ -0,0 +1,188 @@
1
+ import { CommentAccessRefused as e, CommentNotFound as t, CommentNotYours as n } from "./errors.js";
2
+ import { Schema as r } from "effect";
3
+ import { defineMutation as i, defineQuery as a, reactivityChannel as o } from "@voltro/protocol";
4
+ //#region src/quote.ts
5
+ var s = 1e3, c = 32, l = (e, t, n) => {
6
+ let r = Math.max(0, Math.min(t, n)), i = Math.min(e.length, Math.max(t, n), r + s), a = e.slice(Math.max(0, r - c), r), o = e.slice(i, i + c);
7
+ return {
8
+ exact: e.slice(r, i),
9
+ ...a === "" ? {} : { prefix: a },
10
+ ...o === "" ? {} : { suffix: o }
11
+ };
12
+ }, u = (e, t) => {
13
+ if (t.exact === "") return null;
14
+ let n = null;
15
+ for (let r = e.indexOf(t.exact); r !== -1; r = e.indexOf(t.exact, r + 1)) {
16
+ let i = e.slice(0, r), a = e.slice(r + t.exact.length), o = (t.prefix !== void 0 && i.endsWith(t.prefix) ? 1 : 0) + (t.suffix !== void 0 && a.startsWith(t.suffix) ? 1 : 0);
17
+ (n === null || o > n.score) && (n = {
18
+ start: r,
19
+ score: o
20
+ });
21
+ }
22
+ return n === null ? null : {
23
+ start: n.start,
24
+ end: n.start + t.exact.length
25
+ };
26
+ }, d = "@voltro/plugin-comments", f = o("comments"), p = r.Struct({
27
+ id: r.String,
28
+ threadId: r.String,
29
+ body: r.String,
30
+ authorSubjectId: r.String,
31
+ mentions: r.Array(r.String),
32
+ editedAt: r.NullOr(r.String),
33
+ createdAt: r.String,
34
+ reactions: r.Array(r.Struct({
35
+ emoji: r.String,
36
+ count: r.Number,
37
+ mine: r.Boolean
38
+ }))
39
+ }), m = r.Struct({
40
+ exact: r.String.pipe(r.minLength(1), r.maxLength(s)),
41
+ prefix: r.optional(r.String.pipe(r.maxLength(32))),
42
+ suffix: r.optional(r.String.pipe(r.maxLength(32)))
43
+ }), h = r.Struct({
44
+ id: r.String,
45
+ anchor: r.String,
46
+ quote: r.NullOr(m),
47
+ status: r.Literal("open", "resolved"),
48
+ createdBy: r.String,
49
+ createdAt: r.String,
50
+ comments: r.Array(p),
51
+ unreadCount: r.Number
52
+ }), g = a({
53
+ name: "comments.list",
54
+ source: f,
55
+ input: r.Struct({ anchor: r.String }),
56
+ output: r.Array(h),
57
+ error: e,
58
+ 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"
59
+ }), _ = i({
60
+ name: "comments.create",
61
+ input: r.Struct({
62
+ anchor: r.String,
63
+ body: r.String,
64
+ threadId: r.optional(r.String),
65
+ mentions: r.optional(r.Array(r.String)),
66
+ quote: r.optional(m)
67
+ }),
68
+ output: r.Struct({
69
+ threadId: r.String,
70
+ commentId: r.String
71
+ }),
72
+ error: r.Union(e, t),
73
+ openAccess: "delegated: the executor runs the app-declared anchor access rule before any write; the author is always the calling subject"
74
+ }), v = i({
75
+ name: "comments.edit",
76
+ input: r.Struct({
77
+ commentId: r.String,
78
+ body: r.String
79
+ }),
80
+ output: r.Struct({ ok: r.Boolean }),
81
+ error: r.Union(e, t, n),
82
+ openAccess: "author-scoped: only the comment's own author may edit, checked in the executor on top of the anchor access rule"
83
+ }), y = i({
84
+ name: "comments.resolve",
85
+ input: r.Struct({
86
+ threadId: r.String,
87
+ resolved: r.Boolean
88
+ }),
89
+ output: r.Struct({ ok: r.Boolean }),
90
+ error: r.Union(e, t),
91
+ openAccess: "delegated: anchor access rule in the executor — anyone who may read the anchor may resolve/reopen (the Liveblocks semantic)"
92
+ }), b = i({
93
+ name: "comments.delete",
94
+ input: r.Struct({ commentId: r.String }),
95
+ output: r.Struct({ ok: r.Boolean }),
96
+ error: r.Union(e, t, n),
97
+ openAccess: "author-scoped, with a comments:moderate scope override — both checked in the executor on top of the anchor access rule"
98
+ }), x = i({
99
+ name: "comments.react",
100
+ input: r.Struct({
101
+ commentId: r.String,
102
+ emoji: r.String
103
+ }),
104
+ output: r.Struct({ reacted: r.Boolean }),
105
+ error: r.Union(e, t),
106
+ openAccess: "delegated: anchor access rule in the executor; the toggle writes only the calling subject's own reaction row"
107
+ }), S = i({
108
+ name: "comments.markRead",
109
+ input: r.Struct({ threadId: r.String }),
110
+ output: r.Struct({ ok: r.Boolean }),
111
+ error: t,
112
+ openAccess: "self-scoped write: stamps only the calling subject's own read marker for the thread"
113
+ }), C = a({
114
+ name: "comments.mentionSearch",
115
+ input: r.Struct({ query: r.String }),
116
+ output: r.Array(r.Struct({
117
+ subjectId: r.String,
118
+ label: r.String
119
+ })),
120
+ 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"
121
+ }), w = [
122
+ {
123
+ tag: "comments.list",
124
+ kind: "query",
125
+ import: {
126
+ module: "@voltro/plugin-comments/rpc",
127
+ name: "listDescriptor"
128
+ }
129
+ },
130
+ {
131
+ tag: "comments.create",
132
+ kind: "mutation",
133
+ import: {
134
+ module: "@voltro/plugin-comments/rpc",
135
+ name: "createDescriptor"
136
+ }
137
+ },
138
+ {
139
+ tag: "comments.edit",
140
+ kind: "mutation",
141
+ import: {
142
+ module: "@voltro/plugin-comments/rpc",
143
+ name: "editDescriptor"
144
+ }
145
+ },
146
+ {
147
+ tag: "comments.resolve",
148
+ kind: "mutation",
149
+ import: {
150
+ module: "@voltro/plugin-comments/rpc",
151
+ name: "resolveDescriptor"
152
+ }
153
+ },
154
+ {
155
+ tag: "comments.delete",
156
+ kind: "mutation",
157
+ import: {
158
+ module: "@voltro/plugin-comments/rpc",
159
+ name: "deleteDescriptor"
160
+ }
161
+ },
162
+ {
163
+ tag: "comments.react",
164
+ kind: "mutation",
165
+ import: {
166
+ module: "@voltro/plugin-comments/rpc",
167
+ name: "reactDescriptor"
168
+ }
169
+ },
170
+ {
171
+ tag: "comments.markRead",
172
+ kind: "mutation",
173
+ import: {
174
+ module: "@voltro/plugin-comments/rpc",
175
+ name: "markReadDescriptor"
176
+ }
177
+ },
178
+ {
179
+ tag: "comments.mentionSearch",
180
+ kind: "query",
181
+ import: {
182
+ module: "@voltro/plugin-comments/rpc",
183
+ name: "mentionSearchDescriptor"
184
+ }
185
+ }
186
+ ];
187
+ //#endregion
188
+ export { b as a, S as c, y as d, s as f, _ as i, C as l, l as m, f as n, v as o, u as p, w as r, g as s, d as t, x as u };
package/dist/rpc.d.ts CHANGED
@@ -66,6 +66,12 @@ export declare const createDescriptor: MutationProcedureDescriptor<"comments.cre
66
66
  /** Reply into an existing thread; absent opens a new thread. */
67
67
  threadId: Schema.optional<typeof Schema.String>;
68
68
  mentions: Schema.optional<Schema.Array$<typeof Schema.String>>;
69
+ /** A new thread's passage; ignored on a reply. */
70
+ quote: Schema.optional<Schema.Struct<{
71
+ exact: Schema.filter<Schema.filter<typeof Schema.String>>;
72
+ prefix: Schema.optional<Schema.filter<typeof Schema.String>>;
73
+ suffix: Schema.optional<Schema.filter<typeof Schema.String>>;
74
+ }>>;
69
75
  }>, Schema.Struct<{
70
76
  threadId: typeof Schema.String;
71
77
  commentId: typeof Schema.String;
@@ -89,6 +95,12 @@ export declare const listDescriptor: QueryProcedureDescriptor<"comments.list", S
89
95
  }>, Schema.Array$<Schema.Struct<{
90
96
  id: typeof Schema.String;
91
97
  anchor: typeof Schema.String;
98
+ /** The selected passage the thread was opened on, if any. */
99
+ quote: Schema.NullOr<Schema.Struct<{
100
+ exact: Schema.filter<Schema.filter<typeof Schema.String>>;
101
+ prefix: Schema.optional<Schema.filter<typeof Schema.String>>;
102
+ suffix: Schema.optional<Schema.filter<typeof Schema.String>>;
103
+ }>>;
92
104
  status: Schema.Literal<["open", "resolved"]>;
93
105
  createdBy: typeof Schema.String;
94
106
  createdAt: typeof Schema.String;
package/dist/rpc.js CHANGED
@@ -1,161 +1,2 @@
1
- import { CommentAccessRefused as e, CommentNotFound as t, CommentNotYours as n } from "./errors.js";
2
- import { Schema as r } from "effect";
3
- import { defineMutation as i, defineQuery as a, reactivityChannel as o } from "@voltro/protocol";
4
- //#region src/rpc.ts
5
- var s = "@voltro/plugin-comments", c = o("comments"), l = r.Struct({
6
- id: r.String,
7
- threadId: r.String,
8
- body: r.String,
9
- authorSubjectId: r.String,
10
- mentions: r.Array(r.String),
11
- editedAt: r.NullOr(r.String),
12
- createdAt: r.String,
13
- reactions: r.Array(r.Struct({
14
- emoji: r.String,
15
- count: r.Number,
16
- mine: r.Boolean
17
- }))
18
- }), u = r.Struct({
19
- id: r.String,
20
- anchor: r.String,
21
- status: r.Literal("open", "resolved"),
22
- createdBy: r.String,
23
- createdAt: r.String,
24
- comments: r.Array(l),
25
- unreadCount: r.Number
26
- }), d = a({
27
- name: "comments.list",
28
- source: c,
29
- input: r.Struct({ anchor: r.String }),
30
- output: r.Array(u),
31
- error: e,
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
- }), f = i({
34
- name: "comments.create",
35
- input: r.Struct({
36
- anchor: r.String,
37
- body: r.String,
38
- threadId: r.optional(r.String),
39
- mentions: r.optional(r.Array(r.String))
40
- }),
41
- output: r.Struct({
42
- threadId: r.String,
43
- commentId: r.String
44
- }),
45
- error: r.Union(e, t),
46
- openAccess: "delegated: the executor runs the app-declared anchor access rule before any write; the author is always the calling subject"
47
- }), p = i({
48
- name: "comments.edit",
49
- input: r.Struct({
50
- commentId: r.String,
51
- body: r.String
52
- }),
53
- output: r.Struct({ ok: r.Boolean }),
54
- error: r.Union(e, t, n),
55
- openAccess: "author-scoped: only the comment's own author may edit, checked in the executor on top of the anchor access rule"
56
- }), m = i({
57
- name: "comments.resolve",
58
- input: r.Struct({
59
- threadId: r.String,
60
- resolved: r.Boolean
61
- }),
62
- output: r.Struct({ ok: r.Boolean }),
63
- error: r.Union(e, t),
64
- openAccess: "delegated: anchor access rule in the executor — anyone who may read the anchor may resolve/reopen (the Liveblocks semantic)"
65
- }), h = i({
66
- name: "comments.delete",
67
- input: r.Struct({ commentId: r.String }),
68
- output: r.Struct({ ok: r.Boolean }),
69
- error: r.Union(e, t, n),
70
- openAccess: "author-scoped, with a comments:moderate scope override — both checked in the executor on top of the anchor access rule"
71
- }), g = i({
72
- name: "comments.react",
73
- input: r.Struct({
74
- commentId: r.String,
75
- emoji: r.String
76
- }),
77
- output: r.Struct({ reacted: r.Boolean }),
78
- error: r.Union(e, t),
79
- openAccess: "delegated: anchor access rule in the executor; the toggle writes only the calling subject's own reaction row"
80
- }), _ = i({
81
- name: "comments.markRead",
82
- input: r.Struct({ threadId: r.String }),
83
- output: r.Struct({ ok: r.Boolean }),
84
- error: t,
85
- openAccess: "self-scoped write: stamps only the calling subject's own read marker for the thread"
86
- }), v = a({
87
- name: "comments.mentionSearch",
88
- input: r.Struct({ query: r.String }),
89
- output: r.Array(r.Struct({
90
- subjectId: r.String,
91
- label: r.String
92
- })),
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
- }), y = [
95
- {
96
- tag: "comments.list",
97
- kind: "query",
98
- import: {
99
- module: "@voltro/plugin-comments/rpc",
100
- name: "listDescriptor"
101
- }
102
- },
103
- {
104
- tag: "comments.create",
105
- kind: "mutation",
106
- import: {
107
- module: "@voltro/plugin-comments/rpc",
108
- name: "createDescriptor"
109
- }
110
- },
111
- {
112
- tag: "comments.edit",
113
- kind: "mutation",
114
- import: {
115
- module: "@voltro/plugin-comments/rpc",
116
- name: "editDescriptor"
117
- }
118
- },
119
- {
120
- tag: "comments.resolve",
121
- kind: "mutation",
122
- import: {
123
- module: "@voltro/plugin-comments/rpc",
124
- name: "resolveDescriptor"
125
- }
126
- },
127
- {
128
- tag: "comments.delete",
129
- kind: "mutation",
130
- import: {
131
- module: "@voltro/plugin-comments/rpc",
132
- name: "deleteDescriptor"
133
- }
134
- },
135
- {
136
- tag: "comments.react",
137
- kind: "mutation",
138
- import: {
139
- module: "@voltro/plugin-comments/rpc",
140
- name: "reactDescriptor"
141
- }
142
- },
143
- {
144
- tag: "comments.markRead",
145
- kind: "mutation",
146
- import: {
147
- module: "@voltro/plugin-comments/rpc",
148
- name: "markReadDescriptor"
149
- }
150
- },
151
- {
152
- tag: "comments.mentionSearch",
153
- kind: "query",
154
- import: {
155
- module: "@voltro/plugin-comments/rpc",
156
- name: "mentionSearchDescriptor"
157
- }
158
- }
159
- ];
160
- //#endregion
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 };
1
+ import { a as e, c as t, d as n, i as r, l as i, n as a, o, r as s, s as c, t as l, u } from "./rpc-DlFs6xXs.js";
2
+ export { l as BASE_NAME, a as commentsFeed, s as commentsRpcClientImports, r as createDescriptor, e as deleteDescriptor, o as editDescriptor, c as listDescriptor, t as markReadDescriptor, i as mentionSearchDescriptor, u as reactDescriptor, n as resolveDescriptor };
package/dist/web.d.ts CHANGED
@@ -1,3 +1,12 @@
1
+ export declare interface CommentQuote {
2
+ /** The selected text. */
3
+ readonly exact: string;
4
+ /** Up to 32 characters just before it, to tell repeated passages apart. */
5
+ readonly prefix?: string;
6
+ /** Up to 32 characters just after it. */
7
+ readonly suffix?: string;
8
+ }
9
+
1
10
  export declare interface CommentReactionView {
2
11
  readonly emoji: string;
3
12
  readonly count: number;
@@ -15,9 +24,28 @@ export declare interface CommentView {
15
24
  readonly reactions: ReadonlyArray<CommentReactionView>;
16
25
  }
17
26
 
27
+ /**
28
+ * Where `quote` stands in `text` now: `{ start, end }`, or null when the text
29
+ * no longer holds the passage. With the passage more than once, the one whose
30
+ * surroundings match the quote's prefix and suffix best wins (the first on a
31
+ * tie).
32
+ */
33
+ export declare const findQuote: (text: string, quote: CommentQuote) => {
34
+ readonly start: number;
35
+ readonly end: number;
36
+ } | null;
37
+
38
+ /** The longest quoted passage a thread keeps. */
39
+ export declare const QUOTE_MAX_LENGTH = 1000;
40
+
41
+ /** The quote for `text` between `start` and `end` (a text control's selection). */
42
+ export declare const textQuote: (text: string, start: number, end: number) => CommentQuote;
43
+
18
44
  export declare interface ThreadView {
19
45
  readonly id: string;
20
46
  readonly anchor: string;
47
+ /** The selected passage the thread was opened on, if any. */
48
+ readonly quote: CommentQuote | null;
21
49
  readonly status: 'open' | 'resolved';
22
50
  readonly createdBy: string;
23
51
  readonly createdAt: string;
@@ -37,9 +65,11 @@ export declare interface UseCommentsResult {
37
65
  readonly pending: boolean;
38
66
  /** Sum of the threads' unread counters — the badge number. */
39
67
  readonly unreadCount: number;
68
+ /** A reply with `threadId`; a new thread otherwise, on the passage `quote` if given. */
40
69
  readonly create: (body: string, opts?: {
41
70
  threadId?: string;
42
71
  mentions?: ReadonlyArray<string>;
72
+ quote?: CommentQuote;
43
73
  }) => Promise<unknown>;
44
74
  readonly edit: (commentId: string, body: string) => Promise<unknown>;
45
75
  readonly resolve: (threadId: string, resolved?: boolean) => Promise<unknown>;
package/dist/web.js CHANGED
@@ -1,18 +1,19 @@
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";
1
+ import { f as e, m as t, p as n, t as r } from "./rpc-DlFs6xXs.js";
2
+ import { pluginTag as i } from "@voltro/protocol";
3
+ import { useMutation as a, useSubscription as o } from "@voltro/client";
4
4
  //#region src/web.ts
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
+ var s = (e) => i(r, e), c = (e, t = "app") => {
6
+ let { data: n, loading: r } = o(t, s("list"), { anchor: e }), i = a(t, s("create")), c = a(t, s("edit")), l = a(t, s("resolve")), u = a(t, s("delete")), d = a(t, s("react")), f = a(t, s("markRead")), p = n ?? [];
7
7
  return {
8
8
  threads: p,
9
- pending: o,
9
+ pending: r,
10
10
  unreadCount: p.reduce((e, t) => e + t.unreadCount, 0),
11
- create: (t, n) => s.mutate({
11
+ create: (t, n) => i.mutate({
12
12
  anchor: e,
13
13
  body: t,
14
14
  ...n?.threadId === void 0 ? {} : { threadId: n.threadId },
15
- ...n?.mentions === void 0 ? {} : { mentions: n.mentions }
15
+ ...n?.mentions === void 0 ? {} : { mentions: n.mentions },
16
+ ...n?.quote === void 0 ? {} : { quote: n.quote }
16
17
  }),
17
18
  edit: (e, t) => c.mutate({
18
19
  commentId: e,
@@ -29,9 +30,9 @@ var i = (n) => t(e, n), a = (e, t = "app") => {
29
30
  }),
30
31
  markRead: (e) => f.mutate({ threadId: e })
31
32
  };
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 });
33
+ }, l = (e, t, n = "app") => c(e, n).threads.find((e) => e.id === t), u = (e, t = "app") => {
34
+ let { data: n } = o(t, s("mentionSearch"), { query: e });
34
35
  return n ?? [];
35
36
  };
36
37
  //#endregion
37
- export { a as useComments, s as useMentionSearch, o as useThread };
38
+ export { e as QUOTE_MAX_LENGTH, n as findQuote, t as textQuote, c as useComments, u as useMentionSearch, l as useThread };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voltro/plugin-comments",
3
- "version": "0.81.0",
3
+ "version": "0.82.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.81.0",
52
- "@voltro/plugin-notifications": "0.81.0",
53
- "@voltro/protocol": "0.81.0"
51
+ "@voltro/database": "0.82.0",
52
+ "@voltro/plugin-notifications": "0.82.0",
53
+ "@voltro/protocol": "0.82.0"
54
54
  },
55
55
  "peerDependencies": {
56
56
  "effect": "^3.22.2",