convex-feedback 0.1.1 → 0.1.2

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.
@@ -22,28 +22,16 @@ import schema from "./schema.js";
22
22
  export const list = query({
23
23
  args: {
24
24
  paginationOpts: paginationOptsValidator,
25
- entryId: v.string(),
26
- parentCommentId: v.optional(v.string()),
25
+ entryId: v.id("entries"),
26
+ parentCommentId: v.optional(v.id("comments")),
27
27
  sort: commentSortValidator,
28
28
  viewerActorId: v.optional(v.string()),
29
29
  },
30
30
  returns: paginationResultValidator(publicCommentValidator),
31
31
  handler: async (ctx, args) => {
32
- const entryId = ctx.db.normalizeId("entries", args.entryId);
33
- if (entryId === null) throw new ConvexError("Entry not found.");
34
-
35
- const parentCommentId =
36
- args.parentCommentId === undefined
37
- ? undefined
38
- : ctx.db.normalizeId("comments", args.parentCommentId);
39
-
40
- if (parentCommentId === null) {
41
- throw new ConvexError("Parent comment not found.");
42
- }
43
-
44
- if (parentCommentId !== undefined && parentCommentId !== null) {
45
- const parent = await ctx.db.get("comments", parentCommentId);
46
- if (parent === null || parent.entryId !== entryId) {
32
+ if (args.parentCommentId !== undefined) {
33
+ const parent = await ctx.db.get("comments", args.parentCommentId);
34
+ if (parent === null || parent.entryId !== args.entryId) {
47
35
  throw new ConvexError("Parent comment not found on this entry.");
48
36
  }
49
37
  }
@@ -54,14 +42,18 @@ export const list = query({
54
42
  ? await db
55
43
  .query("comments")
56
44
  .withIndex("by_entry_parent_likes", (q) =>
57
- q.eq("entryId", entryId).eq("parentCommentId", parentCommentId),
45
+ q
46
+ .eq("entryId", args.entryId)
47
+ .eq("parentCommentId", args.parentCommentId),
58
48
  )
59
49
  .order("desc")
60
50
  .paginate(args.paginationOpts)
61
51
  : await db
62
52
  .query("comments")
63
53
  .withIndex("by_entry_parent", (q) =>
64
- q.eq("entryId", entryId).eq("parentCommentId", parentCommentId),
54
+ q
55
+ .eq("entryId", args.entryId)
56
+ .eq("parentCommentId", args.parentCommentId),
65
57
  )
66
58
  .order(args.sort === "newest" ? "desc" : "asc")
67
59
  .paginate(args.paginationOpts);
@@ -80,21 +72,19 @@ export const list = query({
80
72
  export const create = mutation({
81
73
  args: {
82
74
  actorId: v.string(),
83
- entryId: v.string(),
84
- parentCommentId: v.optional(v.string()),
75
+ entryId: v.id("entries"),
76
+ parentCommentId: v.optional(v.id("comments")),
85
77
  body: v.string(),
86
78
  maxDepth: v.number(),
87
79
  maxCommentLength: v.number(),
88
80
  },
89
- returns: v.string(),
81
+ returns: v.id("comments"),
90
82
  handler: async (ctx, args) => {
91
83
  assertActorId(args.actorId);
92
84
  assertPositiveInteger(args.maxDepth, "Maximum comment depth");
93
85
  assertPositiveInteger(args.maxCommentLength, "Maximum comment length");
94
86
 
95
- const entryId = ctx.db.normalizeId("entries", args.entryId);
96
- if (entryId === null) throw new ConvexError("Entry not found.");
97
- const entry = await ctx.db.get("entries", entryId);
87
+ const entry = await ctx.db.get("entries", args.entryId);
98
88
  if (entry === null) throw new ConvexError("Entry not found.");
99
89
 
100
90
  const body = normalizeRequiredText(
@@ -103,18 +93,10 @@ export const create = mutation({
103
93
  args.maxCommentLength,
104
94
  );
105
95
 
106
- const parentCommentId =
107
- args.parentCommentId === undefined
108
- ? undefined
109
- : ctx.db.normalizeId("comments", args.parentCommentId);
110
- if (args.parentCommentId !== undefined && parentCommentId === null) {
111
- throw new ConvexError("Parent comment not found.");
112
- }
113
-
114
96
  let depth = 0;
115
- if (parentCommentId !== undefined && parentCommentId !== null) {
116
- const parent = await ctx.db.get("comments", parentCommentId);
117
- if (parent === null || parent.entryId !== entryId) {
97
+ if (args.parentCommentId !== undefined) {
98
+ const parent = await ctx.db.get("comments", args.parentCommentId);
99
+ if (parent === null || parent.entryId !== args.entryId) {
118
100
  throw new ConvexError("Parent comment not found on this entry.");
119
101
  }
120
102
  depth = parent.depth + 1;
@@ -126,10 +108,10 @@ export const create = mutation({
126
108
  }
127
109
 
128
110
  const commentId = await ctx.db.insert("comments", {
129
- entryId,
130
- ...(parentCommentId === undefined || parentCommentId === null
111
+ entryId: args.entryId,
112
+ ...(args.parentCommentId === undefined
131
113
  ? {}
132
- : { parentCommentId }),
114
+ : { parentCommentId: args.parentCommentId }),
133
115
  actorId: args.actorId,
134
116
  depth,
135
117
  body,
@@ -137,14 +119,14 @@ export const create = mutation({
137
119
  replyCount: 0,
138
120
  });
139
121
 
140
- await ctx.db.patch("entries", entryId, {
122
+ await ctx.db.patch("entries", args.entryId, {
141
123
  commentCount: entry.commentCount + 1,
142
124
  });
143
125
 
144
- if (parentCommentId !== undefined && parentCommentId !== null) {
145
- const parent = await ctx.db.get("comments", parentCommentId);
126
+ if (args.parentCommentId !== undefined) {
127
+ const parent = await ctx.db.get("comments", args.parentCommentId);
146
128
  if (parent !== null) {
147
- await ctx.db.patch("comments", parentCommentId, {
129
+ await ctx.db.patch("comments", args.parentCommentId, {
148
130
  replyCount: parent.replyCount + 1,
149
131
  });
150
132
  }
@@ -157,7 +139,7 @@ export const create = mutation({
157
139
  export const update = mutation({
158
140
  args: {
159
141
  actor: actorValidator,
160
- commentId: v.string(),
142
+ commentId: v.id("comments"),
161
143
  body: v.string(),
162
144
  editableByAuthor: v.boolean(),
163
145
  maxCommentLength: v.number(),
@@ -165,9 +147,7 @@ export const update = mutation({
165
147
  returns: v.null(),
166
148
  handler: async (ctx, args) => {
167
149
  assertActorId(args.actor.id);
168
- const commentId = ctx.db.normalizeId("comments", args.commentId);
169
- if (commentId === null) throw new ConvexError("Comment not found.");
170
- const comment = await ctx.db.get("comments", commentId);
150
+ const comment = await ctx.db.get("comments", args.commentId);
171
151
  if (comment === null) throw new ConvexError("Comment not found.");
172
152
  if (comment.deletedAt !== undefined) {
173
153
  throw new ConvexError("Deleted comments cannot be edited.");
@@ -183,7 +163,10 @@ export const update = mutation({
183
163
  "Comment",
184
164
  args.maxCommentLength,
185
165
  );
186
- await ctx.db.patch("comments", commentId, { body, updatedAt: Date.now() });
166
+ await ctx.db.patch("comments", args.commentId, {
167
+ body,
168
+ updatedAt: Date.now(),
169
+ });
187
170
  return null;
188
171
  },
189
172
  });
@@ -191,15 +174,13 @@ export const update = mutation({
191
174
  export const remove = mutation({
192
175
  args: {
193
176
  actor: actorValidator,
194
- commentId: v.string(),
177
+ commentId: v.id("comments"),
195
178
  deletableByAuthor: v.boolean(),
196
179
  },
197
180
  returns: v.null(),
198
181
  handler: async (ctx, args) => {
199
182
  assertActorId(args.actor.id);
200
- const commentId = ctx.db.normalizeId("comments", args.commentId);
201
- if (commentId === null) throw new ConvexError("Comment not found.");
202
- const comment = await ctx.db.get("comments", commentId);
183
+ const comment = await ctx.db.get("comments", args.commentId);
203
184
  if (comment === null) throw new ConvexError("Comment not found.");
204
185
  if (comment.deletedAt !== undefined) return null;
205
186
 
@@ -210,7 +191,7 @@ export const remove = mutation({
210
191
  throw new ConvexError("Not authorized to delete this comment.");
211
192
  }
212
193
 
213
- await ctx.db.patch("comments", commentId, { deletedAt: Date.now() });
194
+ await ctx.db.patch("comments", args.commentId, { deletedAt: Date.now() });
214
195
  return null;
215
196
  },
216
197
  });
@@ -218,15 +199,13 @@ export const remove = mutation({
218
199
  export const setLike = mutation({
219
200
  args: {
220
201
  actorId: v.string(),
221
- commentId: v.string(),
202
+ commentId: v.id("comments"),
222
203
  desiredState: v.boolean(),
223
204
  },
224
205
  returns: v.object({ active: v.boolean(), likeCount: v.number() }),
225
206
  handler: async (ctx, args) => {
226
207
  assertActorId(args.actorId);
227
- const commentId = ctx.db.normalizeId("comments", args.commentId);
228
- if (commentId === null) throw new ConvexError("Comment not found.");
229
- const comment = await ctx.db.get("comments", commentId);
208
+ const comment = await ctx.db.get("comments", args.commentId);
230
209
  if (comment === null) throw new ConvexError("Comment not found.");
231
210
  if (comment.deletedAt !== undefined) {
232
211
  throw new ConvexError("Deleted comments cannot receive likes.");
@@ -235,21 +214,24 @@ export const setLike = mutation({
235
214
  const existing = await ctx.db
236
215
  .query("reactions")
237
216
  .withIndex("by_comment_actor", (q) =>
238
- q.eq("commentId", commentId).eq("actorId", args.actorId),
217
+ q.eq("commentId", args.commentId).eq("actorId", args.actorId),
239
218
  )
240
219
  .unique();
241
220
 
242
221
  if (args.desiredState && existing === null) {
243
- await ctx.db.insert("reactions", { actorId: args.actorId, commentId });
222
+ await ctx.db.insert("reactions", {
223
+ actorId: args.actorId,
224
+ commentId: args.commentId,
225
+ });
244
226
  const likeCount = comment.likeCount + 1;
245
- await ctx.db.patch("comments", commentId, { likeCount });
227
+ await ctx.db.patch("comments", args.commentId, { likeCount });
246
228
  return { active: true, likeCount };
247
229
  }
248
230
 
249
231
  if (!args.desiredState && existing !== null) {
250
232
  await ctx.db.delete("reactions", existing._id);
251
233
  const likeCount = Math.max(0, comment.likeCount - 1);
252
- await ctx.db.patch("comments", commentId, { likeCount });
234
+ await ctx.db.patch("comments", args.commentId, { likeCount });
253
235
  return { active: false, likeCount };
254
236
  }
255
237
 
@@ -198,14 +198,12 @@ export const list = query({
198
198
 
199
199
  export const get = query({
200
200
  args: {
201
- entryId: v.string(),
201
+ entryId: v.id("entries"),
202
202
  viewerActorId: v.optional(v.string()),
203
203
  },
204
204
  returns: v.union(publicEntryValidator, v.null()),
205
205
  handler: async (ctx, args) => {
206
- const entryId = ctx.db.normalizeId("entries", args.entryId);
207
- if (entryId === null) return null;
208
- const entry = await ctx.db.get("entries", entryId);
206
+ const entry = await ctx.db.get("entries", args.entryId);
209
207
  return entry === null
210
208
  ? null
211
209
  : serializeEntry(ctx, entry, args.viewerActorId);
@@ -427,7 +425,7 @@ export const create = mutation({
427
425
  maxTitleLength: v.number(),
428
426
  maxBodyLength: v.number(),
429
427
  },
430
- returns: v.string(),
428
+ returns: v.id("entries"),
431
429
  handler: async (ctx, args) => {
432
430
  assertActorId(args.actorId);
433
431
  if (!args.enabledKinds.includes(args.kind)) {
@@ -465,7 +463,7 @@ export const create = mutation({
465
463
  export const update = mutation({
466
464
  args: {
467
465
  actor: actorValidator,
468
- entryId: v.string(),
466
+ entryId: v.id("entries"),
469
467
  title: v.string(),
470
468
  body: v.string(),
471
469
  editableByAuthor: v.boolean(),
@@ -475,9 +473,7 @@ export const update = mutation({
475
473
  returns: v.null(),
476
474
  handler: async (ctx, args) => {
477
475
  assertActorId(args.actor.id);
478
- const entryId = ctx.db.normalizeId("entries", args.entryId);
479
- if (entryId === null) throw new ConvexError("Entry not found.");
480
- const entry = await ctx.db.get("entries", entryId);
476
+ const entry = await ctx.db.get("entries", args.entryId);
481
477
  if (entry === null) throw new ConvexError("Entry not found.");
482
478
 
483
479
  const canEdit =
@@ -492,7 +488,7 @@ export const update = mutation({
492
488
  );
493
489
  const body = normalizeRequiredText(args.body, "Body", args.maxBodyLength);
494
490
 
495
- await ctx.db.patch("entries", entryId, {
491
+ await ctx.db.patch("entries", args.entryId, {
496
492
  title,
497
493
  body,
498
494
  normalizedTitle: normalizeTitle(title),
@@ -506,7 +502,7 @@ export const update = mutation({
506
502
  export const setStatus = mutation({
507
503
  args: {
508
504
  actor: actorValidator,
509
- entryId: v.string(),
505
+ entryId: v.id("entries"),
510
506
  status: entryStatusValidator,
511
507
  },
512
508
  returns: v.null(),
@@ -514,11 +510,10 @@ export const setStatus = mutation({
514
510
  if (!args.actor.isModerator) {
515
511
  throw new ConvexError("Moderator access is required to change status.");
516
512
  }
517
- const entryId = ctx.db.normalizeId("entries", args.entryId);
518
- if (entryId === null || (await ctx.db.get("entries", entryId)) === null) {
513
+ if ((await ctx.db.get("entries", args.entryId)) === null) {
519
514
  throw new ConvexError("Entry not found.");
520
515
  }
521
- await ctx.db.patch("entries", entryId, {
516
+ await ctx.db.patch("entries", args.entryId, {
522
517
  status: args.status,
523
518
  updatedAt: Date.now(),
524
519
  });
@@ -529,35 +524,36 @@ export const setStatus = mutation({
529
524
  export const setUpvote = mutation({
530
525
  args: {
531
526
  actorId: v.string(),
532
- entryId: v.string(),
527
+ entryId: v.id("entries"),
533
528
  desiredState: v.boolean(),
534
529
  },
535
530
  returns: v.object({ active: v.boolean(), upvoteCount: v.number() }),
536
531
  handler: async (ctx, args) => {
537
532
  assertActorId(args.actorId);
538
- const entryId = ctx.db.normalizeId("entries", args.entryId);
539
- if (entryId === null) throw new ConvexError("Entry not found.");
540
- const entry = await ctx.db.get("entries", entryId);
533
+ const entry = await ctx.db.get("entries", args.entryId);
541
534
  if (entry === null) throw new ConvexError("Entry not found.");
542
535
 
543
536
  const existing = await ctx.db
544
537
  .query("reactions")
545
538
  .withIndex("by_entry_actor", (q) =>
546
- q.eq("entryId", entryId).eq("actorId", args.actorId),
539
+ q.eq("entryId", args.entryId).eq("actorId", args.actorId),
547
540
  )
548
541
  .unique();
549
542
 
550
543
  if (args.desiredState && existing === null) {
551
- await ctx.db.insert("reactions", { actorId: args.actorId, entryId });
544
+ await ctx.db.insert("reactions", {
545
+ actorId: args.actorId,
546
+ entryId: args.entryId,
547
+ });
552
548
  const upvoteCount = entry.upvoteCount + 1;
553
- await ctx.db.patch("entries", entryId, { upvoteCount });
549
+ await ctx.db.patch("entries", args.entryId, { upvoteCount });
554
550
  return { active: true, upvoteCount };
555
551
  }
556
552
 
557
553
  if (!args.desiredState && existing !== null) {
558
554
  await ctx.db.delete("reactions", existing._id);
559
555
  const upvoteCount = Math.max(0, entry.upvoteCount - 1);
560
- await ctx.db.patch("entries", entryId, { upvoteCount });
556
+ await ctx.db.patch("entries", args.entryId, { upvoteCount });
561
557
  return { active: false, upvoteCount };
562
558
  }
563
559
 
@@ -182,9 +182,12 @@ function positivePageSize(value: number | undefined, fallback: number): number {
182
182
  *
183
183
  * Create this once in the consuming application and reuse the returned hook
184
184
  * collection.
185
+ *
186
+ * @typeParam RateLimitResult The validated non-throwing rate-limit rejection
187
+ * type returned by mutation hooks. It is inferred from the supplied API.
185
188
  */
186
- export function createFeedbackHooks(
187
- api: FeedbackPublicApi,
189
+ export function createFeedbackHooks<RateLimitResult = never>(
190
+ api: FeedbackPublicApi<string | undefined, RateLimitResult>,
188
191
  options: FeedbackHooksOptions = {},
189
192
  ) {
190
193
  const entryPageSize = positivePageSize(options.entryPageSize, 20);
@@ -336,5 +339,10 @@ export function createFeedbackHooks(
336
339
  *
337
340
  * This type is intended to be passed to `FeedbackScreen` and other
338
341
  * `convex-feedback-ui` integrations.
342
+ *
343
+ * @typeParam RateLimitResult The validated non-throwing rate-limit rejection
344
+ * type returned by mutation hooks.
339
345
  */
340
- export type FeedbackHooks = ReturnType<typeof createFeedbackHooks>;
346
+ export type FeedbackHooks<RateLimitResult = never> = ReturnType<
347
+ typeof createFeedbackHooks<RateLimitResult>
348
+ >;