convex-feedback 0.2.0-beta.9 → 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
@@ -1,11 +1,11 @@
1
1
  import { type Auth, type DefaultFunctionArgs, type FunctionReference, type GenericDataModel, type GenericMutationCtx, type RegisteredMutation, type RegisteredQuery } from "convex/server";
2
2
  import { type Infer, type Validator, type Value } from "convex/values";
3
3
  import type { ComponentApi } from "../component/_generated/component.js";
4
- import { type FeedbackActor } from "../component/model.js";
4
+ import { type EntryKind, type EntryStatus, type FeedbackActor, type FeedbackMetadata } from "../component/model.js";
5
5
  import type { FeedbackPublicApi } from "./api.js";
6
6
  import { type FeedbackConfigOverrides } from "./config.js";
7
- export type { AdminFeedbackEntry, CommentSort, EntryKind, EntryPriority, EntrySort, EntryStatus, EntryStatusFilter, FeedbackActor, FeedbackComment, FeedbackEntry, FeedbackMetadata, FeedbackMetadataValue, RoadmapItem, RoadmapStatus, SimilarEntriesResult, } from "../component/model.js";
8
- export type { FeedbackPublicApi } from "./api.js";
7
+ export type { AdminFeedbackEntry, CommentSort, EntryKind, EntryPriority, EntrySort, EntryStatus, EntryStatusFilter, FeedbackActivityComment, FeedbackActivityEntry, FeedbackActivityEntryWithContext, FeedbackActor, FeedbackComment, FeedbackCommentReactionTarget, FeedbackEntry, FeedbackEntryReactionTarget, FeedbackMetadata, FeedbackMetadataValue, FeedbackReaction, RoadmapItem, RoadmapStatus, SimilarEntriesResult, } from "../component/model.js";
8
+ export type { AdminListEntriesArgs, AdminSearchEntriesArgs, CreateCommentArgs, CreateEntryArgs, DeleteCommentArgs, DeleteEntryArgs, DetachFeedbackFromRoadmapArgs, FeedbackPublicApi, FindSimilarEntriesArgs, GetEntryArgs, GetRoadmapItemArgs, ListCommentsArgs, ListEntriesArgs, ListRoadmapArgs, ListRoadmapFeedbackArgs, ListUserCommentsArgs, ListUserEntriesArgs, ListUserReactionsArgs, SearchEntriesArgs, SetCommentLikeArgs, SetEntryPriorityArgs, SetEntryStatusArgs, SetEntryUpvoteArgs, UpdateCommentArgs, UpdateEntryArgs, } from "./api.js";
9
9
  export { createFeedbackConfig, defaultFeedbackConfig, type FeedbackConfig, type FeedbackConfigOverrides, } from "./config.js";
10
10
  /**
11
11
  * Minimal host context exposed to the actor resolver.
@@ -108,9 +108,627 @@ export interface ReturningFeedbackRateLimitConfig<ReturnsValidator extends Feedb
108
108
  * @typeParam ReturnsValidator Validator for a non-throwing rejection value.
109
109
  */
110
110
  export type FeedbackRateLimitConfig<ReturnsValidator extends FeedbackRateLimitReturnValidator | undefined = undefined> = ReturnsValidator extends FeedbackRateLimitReturnValidator ? ReturningFeedbackRateLimitConfig<ReturnsValidator> : ThrowingFeedbackRateLimitConfig;
111
+ /**
112
+ * Full host mutation context supplied to every lifecycle callback.
113
+ *
114
+ * This is the host mutation context for the request that triggered the
115
+ * callback. It includes the host database, authentication, storage,
116
+ * scheduler, and nested function-call helpers, so callbacks can apply host
117
+ * business rules or schedule follow-up work.
118
+ *
119
+ * When a callback needs to read or write feedback data, prefer a direct
120
+ * component reference through `ctx.runQuery(components.feedback....)` or
121
+ * `ctx.runMutation(components.feedback....)`. Calling an exposed host API
122
+ * through `api.feedback...` is also valid, but it re-enters the host wrapper
123
+ * and its actor/auth-resolution path. Use the host API when those host-facing
124
+ * semantics are specifically desired.
125
+ *
126
+ * @example Query component data from a callback
127
+ * ```ts
128
+ * import { components } from "./_generated/api";
129
+ *
130
+ * const entry = await ctx.runQuery(components.feedback.entries.get, {
131
+ * entryId: event.entryId,
132
+ * });
133
+ * ```
134
+ */
135
+ export type FeedbackMutationContext = GenericMutationCtx<GenericDataModel>;
136
+ /**
137
+ * Readonly entry creation event passed after auth/rate limiting and before any
138
+ * component work. Return a {@link FeedbackEntryCreatePatch} to transform only
139
+ * the supported fields; mutating `event.input` is neither supported nor used.
140
+ */
141
+ export interface FeedbackEntryBeforeCreateEvent {
142
+ /** Actor resolved by the host for the request being created. */
143
+ readonly actor: Readonly<FeedbackActor>;
144
+ /** Readonly snapshot of the caller's validated mutation input. */
145
+ readonly input: {
146
+ /** Requested entry category. */
147
+ readonly kind: EntryKind;
148
+ /** Title submitted by the caller, before component normalization. */
149
+ readonly title: string;
150
+ /** Body submitted by the caller, before component normalization. */
151
+ readonly body: string;
152
+ /** Optional diagnostic metadata submitted with the entry. */
153
+ readonly metadata?: Readonly<{
154
+ /** Standard metadata collected by the host or UI. */
155
+ standard?: Readonly<Record<string, string | number | boolean>>;
156
+ /** Additional host-defined metadata. */
157
+ additional?: Readonly<Record<string, string | number | boolean>>;
158
+ }>;
159
+ };
160
+ }
161
+ /**
162
+ * Explicit entry creation transformations. Omitted fields retain the original
163
+ * mutation arguments, and all returned values still undergo component
164
+ * validation. Actor identity, IDs, and configured defaults cannot be changed.
165
+ */
166
+ export interface FeedbackEntryCreatePatch {
167
+ /** Replacement entry category. */
168
+ kind?: EntryKind;
169
+ /** Replacement title. */
170
+ title?: string;
171
+ /** Replacement body. */
172
+ body?: string;
173
+ /** Replacement diagnostic metadata. */
174
+ metadata?: FeedbackMetadata;
175
+ }
176
+ /**
177
+ * Readonly comment creation event passed after auth/rate limiting and before
178
+ * component work. `entryId` and `parentCommentId` are immutable relationship
179
+ * fields; only an explicitly returned `body` patch is applied.
180
+ */
181
+ export interface FeedbackCommentBeforeCreateEvent {
182
+ /** Actor resolved by the host for the request being created. */
183
+ readonly actor: Readonly<FeedbackActor>;
184
+ /** Readonly snapshot of the caller's validated mutation input. */
185
+ readonly input: {
186
+ /** ID of the entry that will own the comment. */
187
+ readonly entryId: string;
188
+ /** ID of the parent comment when this input creates a reply. */
189
+ readonly parentCommentId?: string;
190
+ /** Comment body submitted by the caller, before normalization. */
191
+ readonly body: string;
192
+ };
193
+ }
194
+ /**
195
+ * Explicit comment creation transformation. Only `body` is supported and the
196
+ * transformed value still undergoes length and permission validation.
197
+ */
198
+ export interface FeedbackCommentCreatePatch {
199
+ /** Replacement comment body. */
200
+ body?: string;
201
+ }
202
+ /**
203
+ * Helpers supplied to before-create callbacks. `reject(value)` is the only
204
+ * exception translated by callback rejection configuration: it throws a
205
+ * `ConvexError` by default or returns the validated value in return mode.
206
+ * Unexpected callback errors always propagate normally.
207
+ * The third callback parameter is commonly named `handlers`.
208
+ *
209
+ * @typeParam Rejection Value accepted by `reject`. In return mode this is
210
+ * inferred from `callbacks.rejection.returns`.
211
+ */
212
+ export interface FeedbackCallbackHelpers<Rejection = Value> {
213
+ /**
214
+ * Reject the pending entry or comment creation.
215
+ *
216
+ * The call never returns: it throws in the default mode or short-circuits
217
+ * with the validated rejection value in return mode.
218
+ */
219
+ reject: (value: Rejection) => never;
220
+ }
221
+ type MaybePromise<ValueType> = ValueType | Promise<ValueType>;
222
+ /**
223
+ * Runs after actor resolution and rate limiting, before the component entry
224
+ * mutation. It is awaited and may return an explicit creation patch or call
225
+ * `reject()`; it cannot replace normal component validation.
226
+ *
227
+ * @typeParam Rejection Value accepted by `handlers.reject`.
228
+ * @param ctx Host mutation context for the originating request. Use direct
229
+ * component references with `ctx.runQuery`/`ctx.runMutation` for feedback data.
230
+ * @param event Readonly event containing `actor` and `input` (`kind`, `title`,
231
+ * `body`, and optional `metadata`) for this request.
232
+ * @param handlers Callback handlers. Call `handlers.reject(value)` to reject
233
+ * creation.
234
+ * @returns A supported entry patch, or `undefined` to keep the original input.
235
+ *
236
+ * @example Validate and reject an entry before creation
237
+ * ```ts
238
+ * const beforeCreate: FeedbackEntryBeforeCreateCallback = (
239
+ * ctx,
240
+ * event,
241
+ * handlers,
242
+ * ) => {
243
+ * void ctx;
244
+ * if (event.input.title.trim() === "") {
245
+ * handlers.reject("An entry title is required");
246
+ * }
247
+ * };
248
+ * ```
249
+ *
250
+ * @example No-op entry before-create hook
251
+ * ```ts
252
+ * const beforeCreate: FeedbackEntryBeforeCreateCallback = (
253
+ * ctx,
254
+ * event,
255
+ * handlers,
256
+ * ) => {
257
+ * void ctx;
258
+ * void event;
259
+ * void handlers;
260
+ * };
261
+ * ```
262
+ */
263
+ export type FeedbackEntryBeforeCreateCallback<Rejection = Value> = (ctx: FeedbackMutationContext, event: FeedbackEntryBeforeCreateEvent, handlers: FeedbackCallbackHelpers<Rejection>) => MaybePromise<FeedbackEntryCreatePatch | undefined>;
264
+ /**
265
+ * Sanitized persisted entry passed to `entries.afterCreate`.
266
+ *
267
+ * The entry is the authoritative component result after creation and
268
+ * normalization. It is available without another component query.
269
+ */
270
+ export interface FeedbackEntryAfterCreateEvent {
271
+ /** Actor resolved by the host for the creation request. */
272
+ readonly actor: FeedbackActor;
273
+ /** Persisted entry created by the component. */
274
+ readonly entry: {
275
+ /** Public component identifier for the created entry. */
276
+ readonly id: string;
277
+ /** Stable identifier of the actor who created the entry. */
278
+ readonly actorId: string;
279
+ /** Persisted entry category. */
280
+ readonly kind: EntryKind;
281
+ /** Initial workflow status assigned by the component. */
282
+ readonly status: EntryStatus;
283
+ /** Normalized persisted title. */
284
+ readonly title: string;
285
+ /** Normalized persisted body. */
286
+ readonly body: string;
287
+ /** Persisted diagnostic metadata, when supplied. */
288
+ readonly metadata?: FeedbackMetadata;
289
+ /** Authoritative number of upvotes immediately after creation. */
290
+ readonly upvoteCount: number;
291
+ /** Authoritative number of comments immediately after creation. */
292
+ readonly commentCount: number;
293
+ };
294
+ }
295
+ /**
296
+ * Runs exactly once after successful component entry creation and is awaited
297
+ * in the same host mutation. An uncaught error rolls back creation. Rich entry
298
+ * context is requested from the component only when this callback is set.
299
+ *
300
+ * @param ctx Host mutation context for the originating request.
301
+ * @param event Persisted event containing `actor` and the created `entry`
302
+ * (`id`, `actorId`, `kind`, `status`, `title`, `body`, metadata, and counts).
303
+ * @returns Nothing. The callback may be synchronous or asynchronous.
304
+ *
305
+ * @example No-op entry after-create hook
306
+ * ```ts
307
+ * const afterCreate: FeedbackEntryAfterCreateCallback = async (ctx, event) => {
308
+ * void ctx;
309
+ * void event;
310
+ * };
311
+ * ```
312
+ */
313
+ export type FeedbackEntryAfterCreateCallback = (ctx: FeedbackMutationContext, event: FeedbackEntryAfterCreateEvent) => MaybePromise<void>;
314
+ /**
315
+ * Runs after actor resolution and rate limiting, before the component comment
316
+ * mutation. It is awaited and may transform only `body` or call `reject()`.
317
+ *
318
+ * @typeParam Rejection Value accepted by `handlers.reject`.
319
+ * @param ctx Host mutation context for the originating request.
320
+ * @param event Readonly event containing `actor` and `input` (`entryId`,
321
+ * optional `parentCommentId`, and `body`) for this request.
322
+ * @param handlers Callback handlers. Call `handlers.reject(value)` to reject
323
+ * creation.
324
+ * @returns A supported comment patch, or `undefined` to keep the original
325
+ * input.
326
+ *
327
+ * @example No-op comment before-create hook
328
+ * ```ts
329
+ * const beforeCreate: FeedbackCommentBeforeCreateCallback = (
330
+ * ctx,
331
+ * event,
332
+ * handlers,
333
+ * ) => {
334
+ * void ctx;
335
+ * void event;
336
+ * void handlers;
337
+ * };
338
+ * ```
339
+ */
340
+ export type FeedbackCommentBeforeCreateCallback<Rejection = Value> = (ctx: FeedbackMutationContext, event: FeedbackCommentBeforeCreateEvent, handlers: FeedbackCallbackHelpers<Rejection>) => MaybePromise<FeedbackCommentCreatePatch | undefined>;
341
+ /**
342
+ * Persisted comment, entry, and optional parent context passed after creation.
343
+ *
344
+ * All IDs and content in this event come from the successful component write;
345
+ * the optional `parentComment` is present only when the created comment is a
346
+ * reply.
347
+ */
348
+ export interface FeedbackCommentAfterCreateEvent {
349
+ /** Actor resolved by the host for the creation request. */
350
+ readonly actor: FeedbackActor;
351
+ /** Persisted comment created by the component. */
352
+ readonly comment: {
353
+ /** Public component identifier for the created comment. */
354
+ readonly id: string;
355
+ /** Stable identifier of the actor who created the comment. */
356
+ readonly actorId: string;
357
+ /** ID of the entry containing the comment. */
358
+ readonly entryId: string;
359
+ /** ID of the parent comment when this comment is a reply. */
360
+ readonly parentCommentId?: string;
361
+ /** Normalized persisted comment body. */
362
+ readonly body: string;
363
+ /** Nesting depth assigned by the component. */
364
+ readonly depth: number;
365
+ };
366
+ /** Persisted entry containing the created comment. */
367
+ readonly entry: {
368
+ /** Public component identifier for the containing entry. */
369
+ readonly id: string;
370
+ /** Stable identifier of the entry author. */
371
+ readonly actorId: string;
372
+ /** Entry category. */
373
+ readonly kind: EntryKind;
374
+ /** Current workflow status of the entry. */
375
+ readonly status: EntryStatus;
376
+ /** Entry title. */
377
+ readonly title: string;
378
+ };
379
+ /**
380
+ * Minimal persisted parent-comment context, present only for replies.
381
+ */
382
+ readonly parentComment?: {
383
+ /** Public component identifier for the parent comment. */
384
+ readonly id: string;
385
+ /** Stable identifier of the parent comment's author. */
386
+ readonly actorId: string;
387
+ };
388
+ }
389
+ /**
390
+ * Runs exactly once after successful component comment creation and is awaited
391
+ * in the same host mutation. An uncaught error rolls back creation. Rich
392
+ * comment/entry/parent context is requested only when this callback is set.
393
+ *
394
+ * @param ctx Host mutation context for the originating request.
395
+ * @param event Persisted event containing `actor`, `comment`, `entry`, and
396
+ * optional `parentComment` context returned by the component.
397
+ * @returns Nothing. The callback may be synchronous or asynchronous.
398
+ *
399
+ * @example React to a created comment
400
+ * ```ts
401
+ * const afterCreate: FeedbackCommentAfterCreateCallback = async (
402
+ * ctx,
403
+ * event,
404
+ * ) => {
405
+ * await ctx.scheduler.runAfter(0, internal.notifications.commentCreated, {
406
+ * commentId: event.comment.id,
407
+ * entryId: event.entry.id,
408
+ * });
409
+ * };
410
+ * ```
411
+ */
412
+ export type FeedbackCommentAfterCreateCallback = (ctx: FeedbackMutationContext, event: FeedbackCommentAfterCreateEvent) => MaybePromise<void>;
413
+ interface FeedbackReactionChangeBase {
414
+ /** Whether the actor's reaction was added or removed. */
415
+ transition: "added" | "removed";
416
+ /** Whether the actor's reaction is active after the transition. */
417
+ active: boolean;
418
+ /** Reaction count immediately before the transition. */
419
+ previousCount: number;
420
+ /** Authoritative reaction count immediately after the transition. */
421
+ count: number;
422
+ /** Actor who requested the reaction change. */
423
+ actor: FeedbackActor;
424
+ }
425
+ /**
426
+ * Discriminated reaction transition. Events exist only for real state changes:
427
+ * `added` is false→true and `removed` is true→false. `previousCount` is the
428
+ * persisted count immediately before the change and `count` is the final one.
429
+ * Comment-like events intentionally use comment context without reading or
430
+ * serializing the parent entry. The top-level target IDs are convenience
431
+ * aliases for the IDs in the nested target context and always match them.
432
+ */
433
+ export type FeedbackReactionChangeEvent = (FeedbackReactionChangeBase & {
434
+ /** Discriminator for an entry-upvote transition. */
435
+ type: "entry_upvote";
436
+ /** ID of the upvoted entry; always equal to `entry.id`. */
437
+ entryId: string;
438
+ /** Persisted context for the upvoted entry. */
439
+ entry: {
440
+ /** Public component identifier for the upvoted entry. */
441
+ id: string;
442
+ /** Stable identifier of the entry author. */
443
+ actorId: string;
444
+ /** Entry category. */
445
+ kind: EntryKind;
446
+ /** Current workflow status of the entry. */
447
+ status: EntryStatus;
448
+ /** Entry title. */
449
+ title: string;
450
+ };
451
+ }) | (FeedbackReactionChangeBase & {
452
+ /** Discriminator for a comment-like transition. */
453
+ type: "comment_like";
454
+ /** ID of the liked comment; always equal to `comment.id`. */
455
+ commentId: string;
456
+ /** ID of the entry containing the liked comment. */
457
+ entryId: string;
458
+ /** Persisted context for the liked comment. */
459
+ comment: {
460
+ /** Public component identifier for the liked comment. */
461
+ id: string;
462
+ /** Stable identifier of the comment author. */
463
+ actorId: string;
464
+ /** ID of the entry containing the comment. */
465
+ entryId: string;
466
+ /** ID of the parent comment when the target is a reply. */
467
+ parentCommentId?: string;
468
+ /** Persisted comment body. */
469
+ body: string;
470
+ };
471
+ });
472
+ /**
473
+ * Runs after a successful reaction mutation only for an actual transition and
474
+ * is awaited in the same transaction, so uncaught errors roll back the change.
475
+ * Extra reaction context is requested only when this callback is configured.
476
+ *
477
+ * @param ctx Host mutation context for the originating request. For feedback
478
+ * reads, prefer direct component references through `ctx.runQuery` or
479
+ * `ctx.runMutation`.
480
+ * @param event The authoritative event containing `type`, `transition`,
481
+ * `active`, `previousCount`, `count`, `actor`, and the target IDs/context.
482
+ * Use `event.transition` to distinguish additions from removals and the
483
+ * top-level target IDs for convenient lookups.
484
+ * @returns Nothing. The callback may be synchronous or asynchronous.
485
+ *
486
+ * @example Handle a reaction transition and query component data
487
+ * ```ts
488
+ * import { components } from "./_generated/api";
489
+ *
490
+ * const afterChange: FeedbackReactionAfterChangeCallback = async (ctx, event) => {
491
+ * if (event.transition !== "added") return;
492
+ *
493
+ * const entry = await ctx.runQuery(components.feedback.entries.get, {
494
+ * entryId: event.entryId,
495
+ * });
496
+ * if (entry === null) return;
497
+ *
498
+ * const targetId =
499
+ * event.type === "entry_upvote" ? event.entryId : event.commentId;
500
+ * // Use `targetId` and the component result for host-side work.
501
+ * };
502
+ * ```
503
+ *
504
+ * Calling the exposed host API through `api.feedback...` is also valid, but it
505
+ * re-enters the host wrapper and actor/auth-resolution path. Use that route
506
+ * only when those host-facing semantics are desired.
507
+ *
508
+ * @example No-op reaction after-change hook
509
+ * ```ts
510
+ * const afterChange: FeedbackReactionAfterChangeCallback = async (ctx, event) => {
511
+ * void ctx;
512
+ * void event;
513
+ * };
514
+ * ```
515
+ */
516
+ export type FeedbackReactionAfterChangeCallback = (ctx: FeedbackMutationContext, event: FeedbackReactionChangeEvent) => MaybePromise<void>;
517
+ /**
518
+ * Convex validator for an explicit callback rejection returned to the client.
519
+ *
520
+ * Use this with `callbacks.rejection` when a before-create callback should
521
+ * return a typed result instead of throwing a `ConvexError`.
522
+ */
523
+ export type FeedbackCallbackReturnValidator = Validator<Value, "required", string>;
524
+ /**
525
+ * Default callback rejection mode: `handlers.reject(value)` throws a
526
+ * `ConvexError` and prevents the component mutation.
527
+ */
528
+ export interface ThrowingFeedbackCallbackRejectionConfig {
529
+ /** Selects exception-based callback rejection. This is the default. */
530
+ behavior?: "throw";
531
+ /** Return-mode validators are not accepted in throwing mode. */
532
+ returns?: never;
533
+ }
534
+ /**
535
+ * Return mode: `handlers.reject(value)` short-circuits with a validated client
536
+ * result.
537
+ *
538
+ * @typeParam ReturnsValidator Validator for the value passed to `reject`.
539
+ */
540
+ export interface ReturningFeedbackCallbackRejectionConfig<ReturnsValidator extends FeedbackCallbackReturnValidator> {
541
+ /** Selects validated return-mode callback rejection. */
542
+ behavior: "return";
543
+ /** Validator for the value returned when a callback calls `reject`. */
544
+ returns: ReturnsValidator;
545
+ }
546
+ /**
547
+ * Controls only explicit `reject()` calls from entry/comment before callbacks.
548
+ * Runtime/programming errors are never converted. Return-mode inference is
549
+ * added only to `createEntry` and `createComment`.
550
+ *
551
+ * @typeParam ReturnsValidator Validator for the value returned to the client
552
+ * in callback rejection return mode.
553
+ */
554
+ export type FeedbackCallbackRejectionConfig<ReturnsValidator extends FeedbackCallbackReturnValidator | undefined = undefined> = ReturnsValidator extends FeedbackCallbackReturnValidator ? ReturningFeedbackCallbackRejectionConfig<ReturnsValidator> : ThrowingFeedbackCallbackRejectionConfig;
555
+ type CallbackRejectionValue<ReturnsValidator extends FeedbackCallbackReturnValidator | undefined> = ReturnsValidator extends FeedbackCallbackReturnValidator ? Infer<ReturnsValidator> : Value;
556
+ /**
557
+ * Callback properties for entry creation and post-creation work.
558
+ *
559
+ * @typeParam Rejection Value accepted by `beforeCreate`'s `handlers.reject`.
560
+ */
561
+ export interface FeedbackEntryCallbacks<Rejection = Value> {
562
+ /**
563
+ * Validate, reject, or transform an entry before the component creates it.
564
+ *
565
+ * The callback runs after actor resolution and rate limiting. Returned
566
+ * fields are still checked by the component's normal validation.
567
+ * It receives the host `ctx`, readonly `event` (`actor` and `input`), and
568
+ * `handlers` with `handlers.reject(value)`.
569
+ *
570
+ * @example
571
+ * ```ts
572
+ * beforeCreate: (ctx, event, handlers) => {},
573
+ * ```
574
+ */
575
+ beforeCreate?: FeedbackEntryBeforeCreateCallback<Rejection>;
576
+ /**
577
+ * React to an entry after the component has created it successfully.
578
+ *
579
+ * The callback is awaited in the originating host mutation; an uncaught
580
+ * error rolls back the entry creation.
581
+ * It receives the host `ctx` and persisted `event` containing the created
582
+ * actor and entry.
583
+ *
584
+ * @example
585
+ * ```ts
586
+ * afterCreate: async (ctx, event) => {},
587
+ * ```
588
+ */
589
+ afterCreate?: FeedbackEntryAfterCreateCallback;
590
+ }
591
+ /**
592
+ * Callback properties for comment and reply creation.
593
+ *
594
+ * @typeParam Rejection Value accepted by `beforeCreate`'s `handlers.reject`.
595
+ */
596
+ export interface FeedbackCommentCallbacks<Rejection = Value> {
597
+ /**
598
+ * Validate, reject, or transform a comment body before the component
599
+ * creates the comment or reply.
600
+ * It receives the host `ctx`, readonly `event` (`actor` and `input`), and
601
+ * `handlers` with `handlers.reject(value)`.
602
+ *
603
+ * @example
604
+ * ```ts
605
+ * beforeCreate: (ctx, event, handlers) => {},
606
+ * ```
607
+ */
608
+ beforeCreate?: FeedbackCommentBeforeCreateCallback<Rejection>;
609
+ /**
610
+ * React to a comment or reply after the component has created it
611
+ * successfully.
612
+ *
613
+ * The event includes the created comment, its containing entry, and parent
614
+ * comment context when the created comment is a reply.
615
+ * The parameters are the host `ctx` and persisted `event`.
616
+ *
617
+ * @example
618
+ * ```ts
619
+ * afterCreate: async (ctx, event) => {},
620
+ * ```
621
+ */
622
+ afterCreate?: FeedbackCommentAfterCreateCallback;
623
+ }
624
+ /** Callback properties for entry upvotes and comment likes. */
625
+ export interface FeedbackReactionCallbacks {
626
+ /**
627
+ * React to a real reaction transition after the component mutation.
628
+ *
629
+ * The event contains authoritative counts and direct target IDs. It also
630
+ * retains the nested `entry` or `comment` context for existing consumers.
631
+ * The parameters are the host `ctx` and transition `event`.
632
+ *
633
+ * @example
634
+ * ```ts
635
+ * afterChange: async (ctx, event) => {},
636
+ * ```
637
+ */
638
+ afterChange?: FeedbackReactionAfterChangeCallback;
639
+ }
640
+ /**
641
+ * Type of the lifecycle callback configuration accepted by
642
+ * `options.callbacks`.
643
+ *
644
+ * See the `entries`, `comments`, and `reactions` properties for the callback
645
+ * group documentation shown directly in configuration IntelliSense.
646
+ *
647
+ * @typeParam ReturnsValidator Validator that defines the value returned by
648
+ * `handlers.reject` when callback rejection uses return mode.
649
+ */
650
+ export type FeedbackCallbacks<ReturnsValidator extends FeedbackCallbackReturnValidator | undefined = undefined> = {
651
+ /**
652
+ * Lifecycle callbacks for entries.
653
+ *
654
+ * `beforeCreate` runs after actor resolution and rate limiting, and can
655
+ * validate, reject, or transform an entry before the component writes it.
656
+ * `afterCreate` runs after a successful component write and is awaited in
657
+ * the originating host mutation.
658
+ *
659
+ * @example
660
+ * ```ts
661
+ * entries: {
662
+ * beforeCreate: (ctx, event, handlers) => {
663
+ * const titleContainsProfanity = checkForProfanity(event.input.title);
664
+ * const bodyContainsProfanity = checkForProfanity(event.input.body);
665
+ *
666
+ * if (titleContainsProfanity || bodyContainsProfanity) {
667
+ * handlers.reject({
668
+ * kind: "profanity_detected",
669
+ * reason: "Entry content contains profanity",
670
+ * });
671
+ * }
672
+ * },
673
+ * }
674
+ * ```
675
+ */
676
+ entries?: FeedbackEntryCallbacks<CallbackRejectionValue<ReturnsValidator>>;
677
+ /**
678
+ * Lifecycle callbacks for comments and replies.
679
+ *
680
+ * `beforeCreate` can validate, reject, or transform the comment body.
681
+ * `afterCreate` receives the persisted comment, its containing entry, and
682
+ * parent-comment context when the created comment is a reply.
683
+ *
684
+ * @example
685
+ * ```ts
686
+ * comments: {
687
+ * afterCreate: async (ctx, event) => {
688
+ * await ctx.scheduler.runAfter(0, internal.notifications.commentCreated, {
689
+ * commentId: event.comment.id,
690
+ * entryId: event.entry.id,
691
+ * });
692
+ * },
693
+ * }
694
+ * ```
695
+ */
696
+ comments?: FeedbackCommentCallbacks<CallbackRejectionValue<ReturnsValidator>>;
697
+ /**
698
+ * Lifecycle callbacks for entry upvotes and comment likes.
699
+ *
700
+ * `afterChange` runs only for a real `added` or `removed` transition.
701
+ * Repeated requests for the current state remain idempotent and do not
702
+ * invoke it. Direct target IDs are available alongside nested context.
703
+ *
704
+ * @example
705
+ * ```ts
706
+ * reactions: {
707
+ * afterChange: async (ctx, event) => {
708
+ * if (event.transition !== "added") return;
709
+ *
710
+ * const docId = event.type === "entry_upvote" ? event.entryId : event.commentId;
711
+ * // Notify the target author using docId.
712
+ * },
713
+ * }
714
+ * ```
715
+ */
716
+ reactions?: FeedbackReactionCallbacks;
717
+ } & (ReturnsValidator extends FeedbackCallbackReturnValidator ? {
718
+ /**
719
+ * Validated return-mode behavior for explicit `beforeCreate` rejects.
720
+ */
721
+ rejection: ReturningFeedbackCallbackRejectionConfig<ReturnsValidator>;
722
+ } : {
723
+ /**
724
+ * Optional rejection behavior for explicit `beforeCreate` rejects.
725
+ * Defaults to throwing a `ConvexError`.
726
+ */
727
+ rejection?: ThrowingFeedbackCallbackRejectionConfig;
728
+ });
111
729
  type RegisteredFeedbackFunction<Function> = Function extends FunctionReference<"mutation", "public", infer Args, infer Result> ? Args extends DefaultFunctionArgs ? RegisteredMutation<"public", Args, Promise<Result>> : never : Function extends FunctionReference<"query", "public", infer Args, infer Result> ? Args extends DefaultFunctionArgs ? RegisteredQuery<"public", Args, Promise<Result>> : never : never;
112
- type ExposedFeedbackApi<RateLimitResult> = {
113
- [FunctionName in keyof FeedbackPublicApi<string | undefined, RateLimitResult>]: RegisteredFeedbackFunction<FeedbackPublicApi<string | undefined, RateLimitResult>[FunctionName]>;
730
+ type ExposedFeedbackApi<RateLimitResult, CallbackRejectionResult> = {
731
+ [FunctionName in keyof FeedbackPublicApi<string | undefined, RateLimitResult, CallbackRejectionResult>]: RegisteredFeedbackFunction<FeedbackPublicApi<string | undefined, RateLimitResult, CallbackRejectionResult>[FunctionName]>;
114
732
  };
115
733
  interface ExposeFeedbackOptionsBase {
116
734
  /**
@@ -128,8 +746,74 @@ interface ExposeFeedbackOptionsBase {
128
746
  */
129
747
  config?: FeedbackConfigOverrides;
130
748
  }
131
- /** Options for the default mode, where limiter functions reject by throwing. */
132
- export type ThrowingExposeFeedbackOptions = Omit<ExposeFeedbackOptionsBase, "config"> & {
749
+ type FeedbackCallbackOptions<ReturnsValidator extends FeedbackCallbackReturnValidator | undefined> = ReturnsValidator extends FeedbackCallbackReturnValidator ? {
750
+ /**
751
+ * Host lifecycle callbacks grouped by domain with validated return-mode
752
+ * rejection.
753
+ *
754
+ * Creation callbacks run in the order actor/auth → rate limiting →
755
+ * `beforeCreate` → component mutation → `afterCreate`. Reaction
756
+ * callbacks run after the component mutation only for a real transition.
757
+ * All callbacks are awaited in the originating host mutation.
758
+ *
759
+ * For feedback data, prefer direct component references such as
760
+ * `ctx.runQuery(components.feedback.entries.get, ...)`. Calling an
761
+ * exposed host API through `api.feedback...` is also valid, but it
762
+ * re-enters the host wrapper and actor/auth-resolution path; use it only
763
+ * when those host-facing semantics are desired.
764
+ *
765
+ * @example Configure entry validation
766
+ * ```ts
767
+ * callbacks: {
768
+ * entries: {
769
+ * beforeCreate: (ctx, event, handlers) => {
770
+ * void ctx;
771
+ * if (event.input.title.trim() === "") {
772
+ * handlers.reject("A title is required");
773
+ * }
774
+ * },
775
+ * },
776
+ * }
777
+ * ```
778
+ */
779
+ callbacks: FeedbackCallbacks<ReturnsValidator>;
780
+ } : {
781
+ /**
782
+ * Optional host lifecycle callbacks grouped by domain. Explicit
783
+ * rejection throws by default.
784
+ *
785
+ * Creation callbacks run in the order actor/auth → rate limiting →
786
+ * `beforeCreate` → component mutation → `afterCreate`. Reaction
787
+ * callbacks run after the component mutation only for a real transition.
788
+ * All callbacks are awaited in the originating host mutation.
789
+ *
790
+ * For feedback data, prefer direct component references such as
791
+ * `ctx.runQuery(components.feedback.entries.get, ...)`. Calling an
792
+ * exposed host API through `api.feedback...` is also valid, but it
793
+ * re-enters the host wrapper and actor/auth-resolution path; use it only
794
+ * when those host-facing semantics are desired.
795
+ *
796
+ * @example Configure entry validation
797
+ * ```ts
798
+ * callbacks: {
799
+ * entries: {
800
+ * beforeCreate: (ctx, event, handlers) => {
801
+ * void ctx;
802
+ * if (event.input.title.trim() === "") {
803
+ * handlers.reject("A title is required");
804
+ * }
805
+ * },
806
+ * },
807
+ * }
808
+ * ```
809
+ */
810
+ callbacks?: FeedbackCallbacks;
811
+ };
812
+ /**
813
+ * Exposure options with throwing rate limits and optional lifecycle callbacks.
814
+ * Callback execution/rollback semantics are documented by {@link FeedbackCallbacks}.
815
+ */
816
+ export type ThrowingExposeFeedbackOptions<CallbackReturnsValidator extends FeedbackCallbackReturnValidator | undefined = undefined> = Omit<ExposeFeedbackOptionsBase, "config"> & {
133
817
  /** Optional throwing rate limiters for the component's mutation groups. */
134
818
  rateLimiters?: FeedbackRateLimiters;
135
819
  /** Optional component behavior and throwing rate-limit overrides. */
@@ -142,9 +826,12 @@ export type ThrowingExposeFeedbackOptions = Omit<ExposeFeedbackOptionsBase, "con
142
826
  */
143
827
  rateLimiting?: FeedbackRateLimitConfig;
144
828
  };
145
- };
146
- /** Options for returning a validated rejection value instead of throwing. */
147
- export type ReturningExposeFeedbackOptions<ReturnsValidator extends FeedbackRateLimitReturnValidator> = Omit<ExposeFeedbackOptionsBase, "config"> & {
829
+ } & FeedbackCallbackOptions<CallbackReturnsValidator>;
830
+ /**
831
+ * Exposure options for validated rate-limit returns plus optional lifecycle
832
+ * callbacks and independently inferred callback-rejection behavior.
833
+ */
834
+ export type ReturningExposeFeedbackOptions<ReturnsValidator extends FeedbackRateLimitReturnValidator, CallbackReturnsValidator extends FeedbackCallbackReturnValidator | undefined = undefined> = Omit<ExposeFeedbackOptionsBase, "config"> & {
148
835
  /**
149
836
  * Optional returning rate limiters for the component's mutation groups.
150
837
  *
@@ -162,15 +849,16 @@ export type ReturningExposeFeedbackOptions<ReturnsValidator extends FeedbackRate
162
849
  */
163
850
  rateLimiting: FeedbackRateLimitConfig<ReturnsValidator>;
164
851
  };
165
- };
852
+ } & FeedbackCallbackOptions<CallbackReturnsValidator>;
166
853
  /**
167
854
  * Configuration used when exposing feedback functions from the host app.
168
855
  *
169
856
  * When `ReturnsValidator` is omitted, limiter functions use throwing behavior.
170
857
  * Supplying a validator selects return behavior and adds its inferred value to
171
- * the exposed mutation result types.
858
+ * the exposed mutation result types. `CallbackReturnsValidator` independently
859
+ * controls explicit before-callback rejection inference.
172
860
  */
173
- export type ExposeFeedbackOptions<ReturnsValidator extends FeedbackRateLimitReturnValidator | undefined = undefined> = ReturnsValidator extends FeedbackRateLimitReturnValidator ? ReturningExposeFeedbackOptions<ReturnsValidator> : ThrowingExposeFeedbackOptions;
861
+ export type ExposeFeedbackOptions<ReturnsValidator extends FeedbackRateLimitReturnValidator | undefined = undefined, CallbackReturnsValidator extends FeedbackCallbackReturnValidator | undefined = undefined> = ReturnsValidator extends FeedbackRateLimitReturnValidator ? ReturningExposeFeedbackOptions<ReturnsValidator, CallbackReturnsValidator> : ThrowingExposeFeedbackOptions<CallbackReturnsValidator>;
174
862
  /**
175
863
  * Exposes the feedback component through host queries and mutations.
176
864
  *
@@ -179,7 +867,11 @@ export type ExposeFeedbackOptions<ReturnsValidator extends FeedbackRateLimitRetu
179
867
  * `returns` validator and adds that validator's inferred type to every
180
868
  * mutation result.
181
869
  */
182
- export declare function exposeFeedbackApi<Name extends string | undefined, ReturnsValidator extends FeedbackRateLimitReturnValidator>(component: ComponentApi<Name>, options: ReturningExposeFeedbackOptions<ReturnsValidator>): ExposedFeedbackApi<Infer<ReturnsValidator>>;
870
+ export declare function exposeFeedbackApi<Name extends string | undefined, RateReturnsValidator extends FeedbackRateLimitReturnValidator, CallbackReturnsValidator extends FeedbackCallbackReturnValidator>(component: ComponentApi<Name>, options: ReturningExposeFeedbackOptions<RateReturnsValidator, CallbackReturnsValidator>): ExposedFeedbackApi<Infer<RateReturnsValidator>, Infer<CallbackReturnsValidator>>;
871
+ /** Exposes returning rate limits with throwing callback rejections. */
872
+ export declare function exposeFeedbackApi<Name extends string | undefined, RateReturnsValidator extends FeedbackRateLimitReturnValidator>(component: ComponentApi<Name>, options: ReturningExposeFeedbackOptions<RateReturnsValidator>): ExposedFeedbackApi<Infer<RateReturnsValidator>, never>;
873
+ /** Exposes throwing rate limits with returning callback rejections. */
874
+ export declare function exposeFeedbackApi<Name extends string | undefined, CallbackReturnsValidator extends FeedbackCallbackReturnValidator>(component: ComponentApi<Name>, options: ThrowingExposeFeedbackOptions<CallbackReturnsValidator>): ExposedFeedbackApi<never, Infer<CallbackReturnsValidator>>;
183
875
  /** Exposes feedback using optional throwing rate limiters. */
184
- export declare function exposeFeedbackApi<Name extends string | undefined>(component: ComponentApi<Name>, options: ThrowingExposeFeedbackOptions): ExposedFeedbackApi<never>;
876
+ export declare function exposeFeedbackApi<Name extends string | undefined>(component: ComponentApi<Name>, options: ThrowingExposeFeedbackOptions): ExposedFeedbackApi<never, never>;
185
877
  //# sourceMappingURL=index.d.ts.map