convex-feedback 0.2.0-beta.8 → 0.2.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 (81) hide show
  1. package/README.md +398 -7
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/client/api.d.ts +49 -9
  4. package/dist/client/api.d.ts.map +1 -1
  5. package/dist/client/config.d.ts +1 -3
  6. package/dist/client/config.d.ts.map +1 -1
  7. package/dist/client/config.js.map +1 -1
  8. package/dist/client/index.d.ts +707 -15
  9. package/dist/client/index.d.ts.map +1 -1
  10. package/dist/client/index.js +243 -12
  11. package/dist/client/index.js.map +1 -1
  12. package/dist/component/_generated/api.d.ts +2 -0
  13. package/dist/component/_generated/api.d.ts.map +1 -1
  14. package/dist/component/_generated/api.js.map +1 -1
  15. package/dist/component/_generated/component.d.ts +222 -4
  16. package/dist/component/_generated/component.d.ts.map +1 -1
  17. package/dist/component/admin.d.ts +5 -90
  18. package/dist/component/admin.d.ts.map +1 -1
  19. package/dist/component/admin.js +59 -29
  20. package/dist/component/admin.js.map +1 -1
  21. package/dist/component/comments.d.ts +107 -15
  22. package/dist/component/comments.d.ts.map +1 -1
  23. package/dist/component/comments.js +342 -28
  24. package/dist/component/comments.js.map +1 -1
  25. package/dist/component/crons.d.ts.map +1 -1
  26. package/dist/component/crons.js +4 -0
  27. package/dist/component/crons.js.map +1 -1
  28. package/dist/component/entries.d.ts +119 -91
  29. package/dist/component/entries.d.ts.map +1 -1
  30. package/dist/component/entries.js +348 -29
  31. package/dist/component/entries.js.map +1 -1
  32. package/dist/component/helpers.d.ts +32 -1
  33. package/dist/component/helpers.d.ts.map +1 -1
  34. package/dist/component/helpers.js +121 -4
  35. package/dist/component/helpers.js.map +1 -1
  36. package/dist/component/migrations.d.ts +24 -0
  37. package/dist/component/migrations.d.ts.map +1 -1
  38. package/dist/component/migrations.js +318 -0
  39. package/dist/component/migrations.js.map +1 -1
  40. package/dist/component/model.d.ts +455 -120
  41. package/dist/component/model.d.ts.map +1 -1
  42. package/dist/component/model.js +70 -2
  43. package/dist/component/model.js.map +1 -1
  44. package/dist/component/reactions.d.ts +37 -0
  45. package/dist/component/reactions.d.ts.map +1 -0
  46. package/dist/component/reactions.js +52 -0
  47. package/dist/component/reactions.js.map +1 -0
  48. package/dist/component/roadmap.d.ts +10 -46
  49. package/dist/component/roadmap.d.ts.map +1 -1
  50. package/dist/component/roadmap.js +239 -21
  51. package/dist/component/roadmap.js.map +1 -1
  52. package/dist/component/schema.d.ts +33 -5
  53. package/dist/component/schema.d.ts.map +1 -1
  54. package/dist/component/schema.js +40 -4
  55. package/dist/component/schema.js.map +1 -1
  56. package/dist/react/index.d.ts +26 -238
  57. package/dist/react/index.d.ts.map +1 -1
  58. package/dist/react/index.js +65 -2
  59. package/dist/react/index.js.map +1 -1
  60. package/dist/test.d.ts +23 -5
  61. package/dist/test.d.ts.map +1 -1
  62. package/package.json +1 -1
  63. package/src/client/README.md +6 -1
  64. package/src/client/api.ts +89 -7
  65. package/src/client/config.ts +1 -3
  66. package/src/client/index.ts +1237 -34
  67. package/src/component/README.md +32 -4
  68. package/src/component/_generated/api.ts +2 -0
  69. package/src/component/_generated/component.ts +290 -8
  70. package/src/component/admin.ts +88 -30
  71. package/src/component/comments.ts +405 -26
  72. package/src/component/crons.ts +10 -0
  73. package/src/component/entries.ts +388 -27
  74. package/src/component/helpers.ts +163 -4
  75. package/src/component/migrations.ts +415 -0
  76. package/src/component/model.ts +381 -117
  77. package/src/component/reactions.ts +60 -0
  78. package/src/component/roadmap.ts +357 -35
  79. package/src/component/schema.ts +40 -4
  80. package/src/react/README.md +5 -0
  81. package/src/react/index.ts +90 -3
@@ -116,14 +116,91 @@ export const publicCommentValidator = v.object({
116
116
  parentCommentId: v.optional(v.string()),
117
117
  actorId: v.string(),
118
118
  depth: v.number(),
119
- body: v.union(v.string(), v.null()),
119
+ body: v.string(),
120
120
  likeCount: v.number(),
121
121
  replyCount: v.number(),
122
122
  updatedAt: v.optional(v.number()),
123
- deletedAt: v.optional(v.number()),
124
123
  viewerHasLiked: v.boolean(),
125
124
  });
126
125
 
126
+ /**
127
+ * Entry activity returned by actor-scoped queries.
128
+ *
129
+ * Unlike the ordinary public entry shape, activity entries do not contain
130
+ * viewer-relative reaction state. The actor is already the subject of the
131
+ * query.
132
+ */
133
+ export const activityEntryValidator = v.object({
134
+ id: v.string(),
135
+ creationTime: v.number(),
136
+ actorId: v.string(),
137
+ kind: entryKindValidator,
138
+ status: entryStatusValidator,
139
+ title: v.string(),
140
+ body: v.string(),
141
+ upvoteCount: v.number(),
142
+ commentCount: v.number(),
143
+ updatedAt: v.optional(v.number()),
144
+ });
145
+
146
+ /**
147
+ * Component-level activity entry shape with retained diagnostic and triage
148
+ * context. The host wrapper deliberately strips these optional fields from
149
+ * its normal actor-facing API.
150
+ */
151
+ export const activityEntryWithContextValidator = activityEntryValidator.extend({
152
+ metadata: v.optional(feedbackMetadataValidator),
153
+ priority: v.optional(entryPriorityValidator),
154
+ roadmap: v.optional(roadmapItemValidator),
155
+ });
156
+
157
+ /** Comment activity with the basic entry context needed by an activity UI. */
158
+ export const activityCommentValidator = v.object({
159
+ id: v.string(),
160
+ creationTime: v.number(),
161
+ entryId: v.string(),
162
+ entryTitle: v.string(),
163
+ parentCommentId: v.optional(v.string()),
164
+ actorId: v.string(),
165
+ depth: v.number(),
166
+ body: v.string(),
167
+ likeCount: v.number(),
168
+ replyCount: v.number(),
169
+ updatedAt: v.optional(v.number()),
170
+ });
171
+
172
+ /** Resolved target context for an entry-upvote activity record. */
173
+ export const entryReactionTargetValidator = v.object({
174
+ id: v.string(),
175
+ title: v.string(),
176
+ kind: entryKindValidator,
177
+ status: entryStatusValidator,
178
+ });
179
+
180
+ /** Resolved target context for a comment-like activity record. */
181
+ export const commentReactionTargetValidator = v.object({
182
+ id: v.string(),
183
+ body: v.string(),
184
+ entryId: v.string(),
185
+ entryTitle: v.string(),
186
+ });
187
+
188
+ /** A reaction activity record with a live, resolved target. */
189
+ export const feedbackReactionValidator = v.union(
190
+ v.object({
191
+ type: v.literal("entry_upvote"),
192
+ id: v.string(),
193
+ creationTime: v.number(),
194
+ entry: entryReactionTargetValidator,
195
+ }),
196
+ v.object({
197
+ type: v.literal("comment_like"),
198
+ id: v.string(),
199
+ creationTime: v.number(),
200
+ comment: commentReactionTargetValidator,
201
+ }),
202
+ );
203
+
127
204
  export const similarEntriesValidator = v.object({
128
205
  exact: v.array(publicEntryValidator),
129
206
  similar: v.array(publicEntryValidator),
@@ -135,6 +212,20 @@ export const adminEntryValidator = publicEntryValidator.extend({
135
212
  metadata: v.optional(feedbackMetadataValidator),
136
213
  });
137
214
 
215
+ type InferredFeedbackActor = Infer<typeof actorValidator>;
216
+ type InferredRoadmapItem = Infer<typeof roadmapItemValidator>;
217
+ type InferredFeedbackMetadata = Infer<typeof feedbackMetadataValidator>;
218
+ type InferredFeedbackEntry = Infer<typeof publicEntryValidator>;
219
+ type InferredAdminFeedbackEntry = Infer<typeof adminEntryValidator>;
220
+ type InferredFeedbackComment = Infer<typeof publicCommentValidator>;
221
+ type InferredActivityEntry = Infer<typeof activityEntryValidator>;
222
+ type InferredActivityEntryWithContext = Infer<
223
+ typeof activityEntryWithContextValidator
224
+ >;
225
+ type InferredActivityComment = Infer<typeof activityCommentValidator>;
226
+ type InferredFeedbackReaction = Infer<typeof feedbackReactionValidator>;
227
+ type InferredSimilarEntriesResult = Infer<typeof similarEntriesValidator>;
228
+
138
229
  /**
139
230
  * Category of feedback represented by an entry.
140
231
  *
@@ -177,7 +268,34 @@ export type EntryPriority = Infer<typeof entryPriorityValidator>;
177
268
  export type RoadmapStatus = Infer<typeof roadmapStatusValidator>;
178
269
 
179
270
  /** Admin-managed roadmap item and its current attached-feedback count. */
180
- export type RoadmapItem = Infer<typeof roadmapItemValidator>;
271
+ export interface RoadmapItem {
272
+ /** Public component document identifier. */
273
+ id: InferredRoadmapItem["id"];
274
+
275
+ /** Convex document creation timestamp in milliseconds since the Unix epoch. */
276
+ creationTime: InferredRoadmapItem["creationTime"];
277
+
278
+ /** Roadmap item title. */
279
+ title: InferredRoadmapItem["title"];
280
+
281
+ /** Optional roadmap item description. */
282
+ description?: InferredRoadmapItem["description"];
283
+
284
+ /** Current roadmap workflow stage. */
285
+ status: InferredRoadmapItem["status"];
286
+
287
+ /** Relative ordering position among roadmap items. */
288
+ position: InferredRoadmapItem["position"];
289
+
290
+ /** Millisecond timestamp at which the roadmap item was created. */
291
+ createdAt: InferredRoadmapItem["createdAt"];
292
+
293
+ /** Millisecond timestamp of the latest roadmap item update. */
294
+ updatedAt: InferredRoadmapItem["updatedAt"];
295
+
296
+ /** Number of feedback entries currently attached to the roadmap item. */
297
+ feedbackCount: InferredRoadmapItem["feedbackCount"];
298
+ }
181
299
 
182
300
  /**
183
301
  * Server-side ordering strategy for comments and replies.
@@ -194,21 +312,25 @@ export type CommentSort = Infer<typeof commentSortValidator>;
194
312
  *
195
313
  * The component does not access the host application's authentication system
196
314
  * directly. The host resolves its current user/session into this shape.
197
- *
198
- * @property id
199
- * Stable identifier for the actor. This can be a Clerk ID, Convex Auth ID,
200
- * application user ID, anonymous installation ID, or another stable
201
- * host-controlled identifier.
202
- *
203
- * @property isAdmin
204
- * Whether the actor can perform admin-only actions such as changing entry
205
- * status or modifying content they do not own. When omitted, the deprecated
206
- * `isModerator` field is used.
207
- *
208
- * @property isModerator
209
- * @deprecated Use `isAdmin`.
210
315
  */
211
- export type FeedbackActor = Infer<typeof actorValidator>;
316
+ export interface FeedbackActor {
317
+ /**
318
+ * Stable identifier for the actor. This can be a Clerk ID, Convex Auth ID,
319
+ * application user ID, anonymous installation ID, or another stable
320
+ * host-controlled identifier.
321
+ */
322
+ id: InferredFeedbackActor["id"];
323
+
324
+ /**
325
+ * Whether the actor can perform admin-only actions such as changing entry
326
+ * status or modifying content they do not own. When omitted, the deprecated
327
+ * `isModerator` field is used.
328
+ */
329
+ isAdmin?: InferredFeedbackActor["isAdmin"];
330
+
331
+ /** @deprecated Use `isAdmin`. */
332
+ isModerator?: InferredFeedbackActor["isModerator"];
333
+ }
212
334
 
213
335
  /** Scalar value accepted in entry diagnostic metadata. */
214
336
  export type FeedbackMetadataValue = Infer<
@@ -216,61 +338,111 @@ export type FeedbackMetadataValue = Infer<
216
338
  >;
217
339
 
218
340
  /** Flat metadata values grouped by their source. */
219
- export type FeedbackMetadata = Infer<typeof feedbackMetadataValidator>;
341
+ export interface FeedbackMetadata {
342
+ /** Standard diagnostic metadata collected by the component. */
343
+ standard?: InferredFeedbackMetadata["standard"];
220
344
 
221
- /**
222
- * Public representation of a feedback, feature-request, or bug-report entry.
223
- *
224
- * @property id
225
- * Public component document identifier.
226
- *
227
- * @property creationTime
228
- * Convex document creation timestamp in milliseconds since the Unix epoch.
229
- *
230
- * @property actorId
231
- * Stable identifier of the actor who created the entry.
232
- *
233
- * @property kind
234
- * Entry category.
235
- *
236
- * @property status
237
- * Current workflow status.
238
- *
239
- * @property title
240
- * User-provided entry title.
241
- *
242
- * @property body
243
- * User-provided entry description.
244
- *
245
- * @property upvoteCount
246
- * Denormalized number of actors currently upvoting this entry.
247
- *
248
- * @property commentCount
249
- * Denormalized total number of comments belonging to the entry, including
250
- * nested replies.
251
- *
252
- * @property updatedAt
253
- * Millisecond timestamp of the most recent content update. Absent when the
254
- * entry has never been edited.
255
- *
256
- * @property viewerHasUpvoted
257
- * Whether the actor associated with the current query has upvoted the entry.
258
- * `false` when no viewer actor is available.
259
- *
260
- * @property viewerIsAuthor
261
- * Whether the actor associated with the current query created the entry.
262
- * `false` when no viewer actor is available. This is optional for compatibility
263
- * with hosts that have not yet redeployed the updated wrapper.
264
- *
265
- * @property metadata
266
- * Creation-time diagnostic metadata. Present only when `getEntry` is queried
267
- * by an admin. Ordinary entry lists, searches, and non-admin reads omit
268
- * this property.
269
- */
270
- export type FeedbackEntry = Infer<typeof publicEntryValidator>;
345
+ /** Additional host-provided diagnostic metadata. */
346
+ additional?: InferredFeedbackMetadata["additional"];
347
+ }
348
+
349
+ /** Public representation of a feedback, feature-request, or bug-report entry. */
350
+ export interface FeedbackEntry {
351
+ /** Public component document identifier. */
352
+ id: InferredFeedbackEntry["id"];
353
+
354
+ /** Convex document creation timestamp in milliseconds since the Unix epoch. */
355
+ creationTime: InferredFeedbackEntry["creationTime"];
356
+
357
+ /** Stable identifier of the actor who created the entry. */
358
+ actorId: InferredFeedbackEntry["actorId"];
359
+
360
+ /** Entry category. */
361
+ kind: InferredFeedbackEntry["kind"];
362
+
363
+ /** Current workflow status. */
364
+ status: InferredFeedbackEntry["status"];
365
+
366
+ /** User-provided entry title. */
367
+ title: InferredFeedbackEntry["title"];
368
+
369
+ /** User-provided entry description. */
370
+ body: InferredFeedbackEntry["body"];
371
+
372
+ /** Denormalized number of actors currently upvoting this entry. */
373
+ upvoteCount: InferredFeedbackEntry["upvoteCount"];
374
+
375
+ /** Denormalized total number of comments belonging to the entry, including nested replies. */
376
+ commentCount: InferredFeedbackEntry["commentCount"];
377
+
378
+ /** Millisecond timestamp of the most recent content update. Absent when the entry has never been edited. */
379
+ updatedAt?: InferredFeedbackEntry["updatedAt"];
380
+
381
+ /** Whether the actor associated with the current query has upvoted the entry. `false` when no viewer actor is available. */
382
+ viewerHasUpvoted: InferredFeedbackEntry["viewerHasUpvoted"];
383
+
384
+ /**
385
+ * Whether the actor associated with the current query created the entry.
386
+ * `false` when no viewer actor is available. This is optional for compatibility
387
+ * with hosts that have not yet redeployed the updated wrapper.
388
+ */
389
+ viewerIsAuthor?: InferredFeedbackEntry["viewerIsAuthor"];
390
+
391
+ /**
392
+ * Creation-time diagnostic metadata. Present only when `getEntry` is queried
393
+ * by an admin. Ordinary entry lists, searches, and non-admin reads omit this
394
+ * property.
395
+ */
396
+ metadata?: InferredFeedbackEntry["metadata"];
397
+ }
271
398
 
272
399
  /** Feedback entry enriched with private triage metadata for admin clients. */
273
- export type AdminFeedbackEntry = Infer<typeof adminEntryValidator>;
400
+ export interface AdminFeedbackEntry {
401
+ /** Public component document identifier. */
402
+ id: InferredAdminFeedbackEntry["id"];
403
+
404
+ /** Convex document creation timestamp in milliseconds since the Unix epoch. */
405
+ creationTime: InferredAdminFeedbackEntry["creationTime"];
406
+
407
+ /** Stable identifier of the actor who created the entry. */
408
+ actorId: InferredAdminFeedbackEntry["actorId"];
409
+
410
+ /** Entry category. */
411
+ kind: InferredAdminFeedbackEntry["kind"];
412
+
413
+ /** Current workflow status. */
414
+ status: InferredAdminFeedbackEntry["status"];
415
+
416
+ /** User-provided entry title. */
417
+ title: InferredAdminFeedbackEntry["title"];
418
+
419
+ /** User-provided entry description. */
420
+ body: InferredAdminFeedbackEntry["body"];
421
+
422
+ /** Denormalized number of actors currently upvoting this entry. */
423
+ upvoteCount: InferredAdminFeedbackEntry["upvoteCount"];
424
+
425
+ /** Denormalized total number of comments belonging to the entry, including nested replies. */
426
+ commentCount: InferredAdminFeedbackEntry["commentCount"];
427
+
428
+ /** Millisecond timestamp of the most recent content update. Absent when the entry has never been edited. */
429
+ updatedAt?: InferredAdminFeedbackEntry["updatedAt"];
430
+
431
+ /** Whether the actor associated with the current query has upvoted the entry. `false` when no viewer actor is available. */
432
+ viewerHasUpvoted: InferredAdminFeedbackEntry["viewerHasUpvoted"];
433
+
434
+ /** Whether the actor associated with the current query created the entry. */
435
+ viewerIsAuthor?: InferredAdminFeedbackEntry["viewerIsAuthor"];
436
+
437
+ /** Creation-time diagnostic metadata returned to admin clients. */
438
+ metadata?: InferredAdminFeedbackEntry["metadata"];
439
+
440
+ /** Internal triage priority. */
441
+ priority?: InferredAdminFeedbackEntry["priority"];
442
+
443
+ /** Roadmap item attached to the entry, when one exists. */
444
+ roadmap?: InferredAdminFeedbackEntry["roadmap"];
445
+ }
274
446
 
275
447
  /**
276
448
  * Public representation of a comment or reply.
@@ -278,47 +450,140 @@ export type AdminFeedbackEntry = Infer<typeof adminEntryValidator>;
278
450
  * Comments are returned one level at a time. Child comments are not included
279
451
  * automatically; query them separately using the comment's `id` as
280
452
  * `parentCommentId`.
281
- *
282
- * @property id
283
- * Public component document identifier.
284
- *
285
- * @property creationTime
286
- * Convex document creation timestamp in milliseconds since the Unix epoch.
287
- *
288
- * @property entryId
289
- * Entry this comment belongs to.
290
- *
291
- * @property parentCommentId
292
- * Direct parent comment. Absent for top-level comments.
293
- *
294
- * @property actorId
295
- * Stable identifier of the actor who created the comment.
296
- *
297
- * @property depth
298
- * Zero-based nesting depth. Top-level comments have depth `0`.
299
- *
300
- * @property body
301
- * User-provided comment text. `null` when the comment was soft-deleted so
302
- * nested replies can retain their place in the conversation.
303
- *
304
- * @property likeCount
305
- * Denormalized number of actors currently liking this comment.
306
- *
307
- * @property replyCount
308
- * Number of direct child replies. Descendants below those direct children are
309
- * not included in this count.
310
- *
311
- * @property updatedAt
312
- * Millisecond timestamp of the latest edit, when the comment has been edited.
313
- *
314
- * @property deletedAt
315
- * Millisecond timestamp at which the comment was soft-deleted.
316
- *
317
- * @property viewerHasLiked
318
- * Whether the actor associated with the current query likes this comment.
319
- * `false` when no viewer actor is available.
320
453
  */
321
- export type FeedbackComment = Infer<typeof publicCommentValidator>;
454
+ export interface FeedbackComment {
455
+ /** Public component document identifier. */
456
+ id: InferredFeedbackComment["id"];
457
+
458
+ /** Convex document creation timestamp in milliseconds since the Unix epoch. */
459
+ creationTime: InferredFeedbackComment["creationTime"];
460
+
461
+ /** Entry this comment belongs to. */
462
+ entryId: InferredFeedbackComment["entryId"];
463
+
464
+ /** Direct parent comment. Absent for top-level comments. */
465
+ parentCommentId?: InferredFeedbackComment["parentCommentId"];
466
+
467
+ /** Stable identifier of the actor who created the comment. */
468
+ actorId: InferredFeedbackComment["actorId"];
469
+
470
+ /** Zero-based nesting depth. Top-level comments have depth `0`. */
471
+ depth: InferredFeedbackComment["depth"];
472
+
473
+ /** User-provided comment text. */
474
+ body: InferredFeedbackComment["body"];
475
+
476
+ /** Denormalized number of actors currently liking this comment. */
477
+ likeCount: InferredFeedbackComment["likeCount"];
478
+
479
+ /** Number of direct child replies. Descendants below those direct children are not included in this count. */
480
+ replyCount: InferredFeedbackComment["replyCount"];
481
+
482
+ /** Millisecond timestamp of the latest edit, when the comment has been edited. */
483
+ updatedAt?: InferredFeedbackComment["updatedAt"];
484
+
485
+ /** Whether the actor associated with the current query likes this comment. `false` when no viewer actor is available. */
486
+ viewerHasLiked: InferredFeedbackComment["viewerHasLiked"];
487
+ }
488
+
489
+ /** Entry created by the actor used for an actor-scoped activity query. */
490
+ export interface FeedbackActivityEntry {
491
+ /** Public component document identifier. */
492
+ id: InferredActivityEntry["id"];
493
+
494
+ /** Convex document creation timestamp in milliseconds since the Unix epoch. */
495
+ creationTime: InferredActivityEntry["creationTime"];
496
+
497
+ /** Stable identifier of the actor who created the entry. */
498
+ actorId: InferredActivityEntry["actorId"];
499
+
500
+ /** Entry category. */
501
+ kind: InferredActivityEntry["kind"];
502
+
503
+ /** Current workflow status. */
504
+ status: InferredActivityEntry["status"];
505
+
506
+ /** User-provided entry title. */
507
+ title: InferredActivityEntry["title"];
508
+
509
+ /** User-provided entry description. */
510
+ body: InferredActivityEntry["body"];
511
+
512
+ /** Denormalized number of actors currently upvoting this entry. */
513
+ upvoteCount: InferredActivityEntry["upvoteCount"];
514
+
515
+ /** Denormalized total number of comments belonging to the entry. */
516
+ commentCount: InferredActivityEntry["commentCount"];
517
+
518
+ /** Millisecond timestamp of the most recent content update. */
519
+ updatedAt?: InferredActivityEntry["updatedAt"];
520
+ }
521
+
522
+ /**
523
+ * Component-only activity entry shape with retained diagnostic and triage
524
+ * context. It is used by trusted server consumers, not the normal `listUser`
525
+ * wrappers.
526
+ */
527
+ export interface FeedbackActivityEntryWithContext extends FeedbackActivityEntry {
528
+ metadata?: InferredActivityEntryWithContext["metadata"];
529
+ priority?: InferredActivityEntryWithContext["priority"];
530
+ roadmap?: InferredActivityEntryWithContext["roadmap"];
531
+ }
532
+
533
+ /** Comment created by the actor used for an actor-scoped activity query. */
534
+ export interface FeedbackActivityComment {
535
+ /** Public component document identifier. */
536
+ id: InferredActivityComment["id"];
537
+
538
+ /** Convex document creation timestamp in milliseconds since the Unix epoch. */
539
+ creationTime: InferredActivityComment["creationTime"];
540
+
541
+ /** Entry this comment belongs to. */
542
+ entryId: InferredActivityComment["entryId"];
543
+
544
+ /** Current entry title. Activity rows with missing entries are removed. */
545
+ entryTitle: InferredActivityComment["entryTitle"];
546
+
547
+ /** Direct parent comment. Absent for top-level comments. */
548
+ parentCommentId?: InferredActivityComment["parentCommentId"];
549
+
550
+ /** Stable identifier of the actor who created the comment. */
551
+ actorId: InferredActivityComment["actorId"];
552
+
553
+ /** Zero-based nesting depth. */
554
+ depth: InferredActivityComment["depth"];
555
+
556
+ /** User-provided comment text. */
557
+ body: InferredActivityComment["body"];
558
+
559
+ /** Denormalized number of actors currently liking this comment. */
560
+ likeCount: InferredActivityComment["likeCount"];
561
+
562
+ /** Number of direct child replies. */
563
+ replyCount: InferredActivityComment["replyCount"];
564
+
565
+ /** Millisecond timestamp of the latest edit, when edited. */
566
+ updatedAt?: InferredActivityComment["updatedAt"];
567
+ }
568
+
569
+ /** Resolved entry context for an entry-upvote activity record. */
570
+ export interface FeedbackEntryReactionTarget {
571
+ id: InferredActivityEntry["id"];
572
+ title: string;
573
+ kind: EntryKind;
574
+ status: EntryStatus;
575
+ }
576
+
577
+ /** Resolved comment context for a comment-like activity record. */
578
+ export interface FeedbackCommentReactionTarget {
579
+ id: InferredActivityComment["id"];
580
+ body: string;
581
+ entryId: InferredActivityComment["entryId"];
582
+ entryTitle: string;
583
+ }
584
+
585
+ /** A reaction created by the actor used for an actor-scoped activity query. */
586
+ export type FeedbackReaction = InferredFeedbackReaction;
322
587
 
323
588
  /**
324
589
  * Duplicate-detection result for a proposed entry.
@@ -335,12 +600,11 @@ export type FeedbackComment = Infer<typeof publicCommentValidator>;
335
600
  * - 0 exact matches → `0 exact + at most 3 similar`
336
601
  *
337
602
  * An entry returned in `exact` is never repeated in `similar`.
338
- *
339
- * @property exact
340
- * Entries whose normalized title exactly matches the proposed title.
341
- *
342
- * @property similar
343
- * Full-text matches ordered by search relevance after exact matches have been
344
- * removed.
345
603
  */
346
- export type SimilarEntriesResult = Infer<typeof similarEntriesValidator>;
604
+ export interface SimilarEntriesResult {
605
+ /** Entries whose normalized title exactly matches the proposed title. */
606
+ exact: InferredSimilarEntriesResult["exact"];
607
+
608
+ /** Full-text matches ordered by search relevance after exact matches have been removed. */
609
+ similar: InferredSimilarEntriesResult["similar"];
610
+ }
@@ -0,0 +1,60 @@
1
+ import { stream } from "convex-helpers/server/stream";
2
+ import {
3
+ paginationOptsValidator,
4
+ paginationResultValidator,
5
+ } from "convex/server";
6
+ import { v } from "convex/values";
7
+
8
+ import {
9
+ assertActorId,
10
+ commentIsLive,
11
+ serializeActivityReaction,
12
+ } from "./helpers.js";
13
+ import { feedbackReactionValidator } from "./model.js";
14
+ import { query } from "./_generated/server.js";
15
+ import schema from "./schema.js";
16
+
17
+ /**
18
+ * List reactions created by a known actor. Target documents are loaded once
19
+ * by the stream mapper and reused by serialization. Reactions whose target or
20
+ * owning entry is missing or pending deletion are omitted.
21
+ */
22
+ export const listByActor = query({
23
+ args: {
24
+ paginationOpts: paginationOptsValidator,
25
+ actorId: v.string(),
26
+ },
27
+ returns: paginationResultValidator(feedbackReactionValidator),
28
+ handler: async (ctx, args) => {
29
+ assertActorId(args.actorId);
30
+
31
+ const result = await stream(ctx.db, schema)
32
+ .query("reactions")
33
+ .withIndex("by_actor", (q) => q.eq("actorId", args.actorId))
34
+ .order("desc")
35
+ .map(async (reaction) => {
36
+ if (reaction.entryId !== undefined) {
37
+ const entry = await ctx.db.get("entries", reaction.entryId);
38
+ if (entry === null || entry.deletingAt !== undefined) return null;
39
+ return serializeActivityReaction(reaction, { type: "entry", entry });
40
+ }
41
+ if (reaction.commentId !== undefined) {
42
+ const comment = await ctx.db.get("comments", reaction.commentId);
43
+ if (comment === null || !(await commentIsLive(ctx, comment))) {
44
+ return null;
45
+ }
46
+ const entry = await ctx.db.get("entries", comment.entryId);
47
+ if (entry === null || entry.deletingAt !== undefined) return null;
48
+ return serializeActivityReaction(reaction, {
49
+ type: "comment",
50
+ comment,
51
+ entry,
52
+ });
53
+ }
54
+ return null;
55
+ })
56
+ .paginate(args.paginationOpts);
57
+
58
+ return result;
59
+ },
60
+ });