convex-feedback 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +183 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/client/api.d.ts +72 -0
- package/dist/client/api.d.ts.map +1 -0
- package/dist/client/api.js +2 -0
- package/dist/client/api.js.map +1 -0
- package/dist/client/config.d.ts +37 -0
- package/dist/client/config.d.ts.map +1 -0
- package/dist/client/config.js +36 -0
- package/dist/client/config.js.map +1 -0
- package/dist/client/index.d.ts +186 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +248 -0
- package/dist/client/index.js.map +1 -0
- package/dist/component/_generated/api.d.ts +42 -0
- package/dist/component/_generated/api.d.ts.map +1 -0
- package/dist/component/_generated/api.js +31 -0
- package/dist/component/_generated/api.js.map +1 -0
- package/dist/component/_generated/component.d.ts +233 -0
- package/dist/component/_generated/component.d.ts.map +1 -0
- package/dist/component/_generated/component.js +11 -0
- package/dist/component/_generated/component.js.map +1 -0
- package/dist/component/_generated/dataModel.d.ts +46 -0
- package/dist/component/_generated/dataModel.d.ts.map +1 -0
- package/dist/component/_generated/dataModel.js +11 -0
- package/dist/component/_generated/dataModel.js.map +1 -0
- package/dist/component/_generated/server.d.ts +133 -0
- package/dist/component/_generated/server.d.ts.map +1 -0
- package/dist/component/_generated/server.js +80 -0
- package/dist/component/_generated/server.js.map +1 -0
- package/dist/component/comments.d.ts +68 -0
- package/dist/component/comments.d.ts.map +1 -0
- package/dist/component/comments.js +206 -0
- package/dist/component/comments.js.map +1 -0
- package/dist/component/convex.config.d.ts +3 -0
- package/dist/component/convex.config.d.ts.map +1 -0
- package/dist/component/convex.config.js +3 -0
- package/dist/component/convex.config.js.map +1 -0
- package/dist/component/entries.d.ts +140 -0
- package/dist/component/entries.d.ts.map +1 -0
- package/dist/component/entries.js +302 -0
- package/dist/component/entries.js.map +1 -0
- package/dist/component/helpers.d.ts +10 -0
- package/dist/component/helpers.d.ts.map +1 -0
- package/dist/component/helpers.js +74 -0
- package/dist/component/helpers.js.map +1 -0
- package/dist/component/model.d.ts +176 -0
- package/dist/component/model.d.ts.map +1 -0
- package/dist/component/model.js +41 -0
- package/dist/component/model.js.map +1 -0
- package/dist/component/schema.d.ts +78 -0
- package/dist/component/schema.d.ts.map +1 -0
- package/dist/component/schema.js +56 -0
- package/dist/component/schema.js.map +1 -0
- package/dist/component/types.d.ts +5 -0
- package/dist/component/types.d.ts.map +1 -0
- package/dist/component/types.js +2 -0
- package/dist/component/types.js.map +1 -0
- package/dist/react/index.d.ts +169 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +68 -0
- package/dist/react/index.js.map +1 -0
- package/dist/test.d.ts +88 -0
- package/dist/test.d.ts.map +1 -0
- package/dist/test.js +9 -0
- package/dist/test.js.map +1 -0
- package/package.json +63 -0
- package/src/client/README.md +11 -0
- package/src/client/api.ts +126 -0
- package/src/client/config.ts +80 -0
- package/src/client/index.ts +338 -0
- package/src/component/README.md +13 -0
- package/src/component/_generated/api.ts +58 -0
- package/src/component/_generated/component.ts +339 -0
- package/src/component/_generated/dataModel.ts +60 -0
- package/src/component/_generated/server.ts +169 -0
- package/src/component/comments.ts +258 -0
- package/src/component/convex.config.ts +3 -0
- package/src/component/entries.ts +385 -0
- package/src/component/helpers.ts +105 -0
- package/src/component/model.ts +76 -0
- package/src/component/schema.ts +60 -0
- package/src/component/types.ts +6 -0
- package/src/react/README.md +7 -0
- package/src/react/index.ts +140 -0
- package/src/test.ts +18 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { ConvexError } from "convex/values";
|
|
2
|
+
|
|
3
|
+
import type { DataModel } from "./_generated/dataModel.js";
|
|
4
|
+
import type { QueryCtx } from "./types.js";
|
|
5
|
+
import type { FeedbackComment, FeedbackEntry } from "./model.js";
|
|
6
|
+
|
|
7
|
+
export function normalizeTitle(value: string): string {
|
|
8
|
+
return value.trim().replace(/\s+/g, " ").toLocaleLowerCase("en-US");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function normalizeRequiredText(
|
|
12
|
+
value: string,
|
|
13
|
+
field: string,
|
|
14
|
+
maximumLength: number,
|
|
15
|
+
): string {
|
|
16
|
+
const normalized = value.trim();
|
|
17
|
+
if (normalized.length === 0) {
|
|
18
|
+
throw new ConvexError(`${field} is required.`);
|
|
19
|
+
}
|
|
20
|
+
if (normalized.length > maximumLength) {
|
|
21
|
+
throw new ConvexError(
|
|
22
|
+
`${field} must be ${maximumLength} characters or fewer.`,
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
return normalized;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function assertPositiveInteger(value: number, field: string): void {
|
|
29
|
+
if (!Number.isInteger(value) || value <= 0) {
|
|
30
|
+
throw new ConvexError(`${field} must be a positive integer.`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function assertActorId(actorId: string): void {
|
|
35
|
+
if (actorId.trim().length === 0) {
|
|
36
|
+
throw new ConvexError("Actor id must not be empty.");
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function serializeEntry(
|
|
41
|
+
ctx: QueryCtx,
|
|
42
|
+
entry: DataModel["entries"]["document"],
|
|
43
|
+
viewerActorId: string | undefined,
|
|
44
|
+
): Promise<FeedbackEntry> {
|
|
45
|
+
const reaction =
|
|
46
|
+
viewerActorId !== undefined
|
|
47
|
+
? await ctx.db
|
|
48
|
+
.query("reactions")
|
|
49
|
+
.withIndex("by_entry_actor", (q) =>
|
|
50
|
+
q.eq("entryId", entry._id).eq("actorId", viewerActorId),
|
|
51
|
+
)
|
|
52
|
+
.unique()
|
|
53
|
+
: null;
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
id: entry._id,
|
|
57
|
+
creationTime: entry._creationTime,
|
|
58
|
+
actorId: entry.actorId,
|
|
59
|
+
kind: entry.kind,
|
|
60
|
+
status: entry.status,
|
|
61
|
+
title: entry.title,
|
|
62
|
+
body: entry.body,
|
|
63
|
+
upvoteCount: entry.upvoteCount,
|
|
64
|
+
commentCount: entry.commentCount,
|
|
65
|
+
...(entry.updatedAt === undefined ? {} : { updatedAt: entry.updatedAt }),
|
|
66
|
+
viewerHasUpvoted: reaction !== null,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function serializeComment(
|
|
71
|
+
ctx: QueryCtx,
|
|
72
|
+
comment: DataModel["comments"]["document"],
|
|
73
|
+
viewerActorId: string | undefined,
|
|
74
|
+
): Promise<FeedbackComment> {
|
|
75
|
+
const reaction =
|
|
76
|
+
viewerActorId !== undefined
|
|
77
|
+
? await ctx.db
|
|
78
|
+
.query("reactions")
|
|
79
|
+
.withIndex("by_comment_actor", (q) =>
|
|
80
|
+
q.eq("commentId", comment._id).eq("actorId", viewerActorId),
|
|
81
|
+
)
|
|
82
|
+
.unique()
|
|
83
|
+
: null;
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
id: comment._id,
|
|
87
|
+
creationTime: comment._creationTime,
|
|
88
|
+
entryId: comment.entryId,
|
|
89
|
+
...(comment.parentCommentId === undefined
|
|
90
|
+
? {}
|
|
91
|
+
: { parentCommentId: comment.parentCommentId }),
|
|
92
|
+
actorId: comment.actorId,
|
|
93
|
+
depth: comment.depth,
|
|
94
|
+
body: comment.deletedAt === undefined ? comment.body : null,
|
|
95
|
+
likeCount: comment.likeCount,
|
|
96
|
+
replyCount: comment.replyCount,
|
|
97
|
+
...(comment.updatedAt === undefined
|
|
98
|
+
? {}
|
|
99
|
+
: { updatedAt: comment.updatedAt }),
|
|
100
|
+
...(comment.deletedAt === undefined
|
|
101
|
+
? {}
|
|
102
|
+
: { deletedAt: comment.deletedAt }),
|
|
103
|
+
viewerHasLiked: reaction !== null,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { Infer } from "convex/values";
|
|
2
|
+
import { v } from "convex/values";
|
|
3
|
+
|
|
4
|
+
export const entryKindValidator = v.union(
|
|
5
|
+
v.literal("feedback"),
|
|
6
|
+
v.literal("feature_request"),
|
|
7
|
+
v.literal("bug_report"),
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
export const entryStatusValidator = v.union(
|
|
11
|
+
v.literal("open"),
|
|
12
|
+
v.literal("under_review"),
|
|
13
|
+
v.literal("planned"),
|
|
14
|
+
v.literal("in_progress"),
|
|
15
|
+
v.literal("completed"),
|
|
16
|
+
v.literal("closed"),
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
export const entrySortValidator = v.union(
|
|
20
|
+
v.literal("top"),
|
|
21
|
+
v.literal("newest"),
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
export const commentSortValidator = v.union(
|
|
25
|
+
v.literal("top"),
|
|
26
|
+
v.literal("newest"),
|
|
27
|
+
v.literal("oldest"),
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
export const actorValidator = v.object({
|
|
31
|
+
id: v.string(),
|
|
32
|
+
isModerator: v.boolean(),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
export const publicEntryValidator = v.object({
|
|
36
|
+
id: v.string(),
|
|
37
|
+
creationTime: v.number(),
|
|
38
|
+
actorId: v.string(),
|
|
39
|
+
kind: entryKindValidator,
|
|
40
|
+
status: entryStatusValidator,
|
|
41
|
+
title: v.string(),
|
|
42
|
+
body: v.string(),
|
|
43
|
+
upvoteCount: v.number(),
|
|
44
|
+
commentCount: v.number(),
|
|
45
|
+
updatedAt: v.optional(v.number()),
|
|
46
|
+
viewerHasUpvoted: v.boolean(),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
export const publicCommentValidator = v.object({
|
|
50
|
+
id: v.string(),
|
|
51
|
+
creationTime: v.number(),
|
|
52
|
+
entryId: v.string(),
|
|
53
|
+
parentCommentId: v.optional(v.string()),
|
|
54
|
+
actorId: v.string(),
|
|
55
|
+
depth: v.number(),
|
|
56
|
+
body: v.union(v.string(), v.null()),
|
|
57
|
+
likeCount: v.number(),
|
|
58
|
+
replyCount: v.number(),
|
|
59
|
+
updatedAt: v.optional(v.number()),
|
|
60
|
+
deletedAt: v.optional(v.number()),
|
|
61
|
+
viewerHasLiked: v.boolean(),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
export const similarEntriesValidator = v.object({
|
|
65
|
+
exact: v.array(publicEntryValidator),
|
|
66
|
+
similar: v.array(publicEntryValidator),
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
export type EntryKind = Infer<typeof entryKindValidator>;
|
|
70
|
+
export type EntryStatus = Infer<typeof entryStatusValidator>;
|
|
71
|
+
export type EntrySort = Infer<typeof entrySortValidator>;
|
|
72
|
+
export type CommentSort = Infer<typeof commentSortValidator>;
|
|
73
|
+
export type FeedbackActor = Infer<typeof actorValidator>;
|
|
74
|
+
export type FeedbackEntry = Infer<typeof publicEntryValidator>;
|
|
75
|
+
export type FeedbackComment = Infer<typeof publicCommentValidator>;
|
|
76
|
+
export type SimilarEntriesResult = Infer<typeof similarEntriesValidator>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { defineSchema, defineTable } from "convex/server";
|
|
2
|
+
import { v } from "convex/values";
|
|
3
|
+
|
|
4
|
+
import { entryKindValidator, entryStatusValidator } from "./model.js";
|
|
5
|
+
|
|
6
|
+
const schema = defineSchema({
|
|
7
|
+
entries: defineTable({
|
|
8
|
+
actorId: v.string(),
|
|
9
|
+
kind: entryKindValidator,
|
|
10
|
+
status: entryStatusValidator,
|
|
11
|
+
title: v.string(),
|
|
12
|
+
body: v.string(),
|
|
13
|
+
normalizedTitle: v.string(),
|
|
14
|
+
searchText: v.string(),
|
|
15
|
+
upvoteCount: v.number(),
|
|
16
|
+
commentCount: v.number(),
|
|
17
|
+
updatedAt: v.optional(v.number()),
|
|
18
|
+
})
|
|
19
|
+
.index("by_kind", ["kind"])
|
|
20
|
+
.index("by_status", ["status"])
|
|
21
|
+
.index("by_kind_status", ["kind", "status"])
|
|
22
|
+
.index("by_upvotes", ["upvoteCount"])
|
|
23
|
+
.index("by_kind_upvotes", ["kind", "upvoteCount"])
|
|
24
|
+
.index("by_status_upvotes", ["status", "upvoteCount"])
|
|
25
|
+
.index("by_kind_status_upvotes", ["kind", "status", "upvoteCount"])
|
|
26
|
+
.index("by_normalized_title", ["normalizedTitle"])
|
|
27
|
+
.index("by_kind_normalized_title", ["kind", "normalizedTitle"])
|
|
28
|
+
.searchIndex("search", {
|
|
29
|
+
searchField: "searchText",
|
|
30
|
+
filterFields: ["kind", "status"],
|
|
31
|
+
}),
|
|
32
|
+
|
|
33
|
+
comments: defineTable({
|
|
34
|
+
entryId: v.id("entries"),
|
|
35
|
+
parentCommentId: v.optional(v.id("comments")),
|
|
36
|
+
actorId: v.string(),
|
|
37
|
+
depth: v.number(),
|
|
38
|
+
body: v.string(),
|
|
39
|
+
likeCount: v.number(),
|
|
40
|
+
replyCount: v.number(),
|
|
41
|
+
updatedAt: v.optional(v.number()),
|
|
42
|
+
deletedAt: v.optional(v.number()),
|
|
43
|
+
})
|
|
44
|
+
.index("by_entry_parent", ["entryId", "parentCommentId"])
|
|
45
|
+
.index("by_entry_parent_likes", [
|
|
46
|
+
"entryId",
|
|
47
|
+
"parentCommentId",
|
|
48
|
+
"likeCount",
|
|
49
|
+
]),
|
|
50
|
+
|
|
51
|
+
reactions: defineTable({
|
|
52
|
+
actorId: v.string(),
|
|
53
|
+
entryId: v.optional(v.id("entries")),
|
|
54
|
+
commentId: v.optional(v.id("comments")),
|
|
55
|
+
})
|
|
56
|
+
.index("by_entry_actor", ["entryId", "actorId"])
|
|
57
|
+
.index("by_comment_actor", ["commentId", "actorId"]),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
export default schema;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# React hooks layer
|
|
2
|
+
|
|
3
|
+
`createFeedbackHooks(api, options?)` binds the host application's public Convex API to headless React hooks.
|
|
4
|
+
|
|
5
|
+
The hooks expose entries, search/similarity, lazy direct-child comment pagination, mutations, and the configured client page sizes. Reply queries are not mounted by the hook layer automatically; the UI decides when a comment is expanded.
|
|
6
|
+
|
|
7
|
+
Page-size defaults are client concerns. The server-side component still enforces its configured hard maximum. Keep the two aligned when overriding defaults.
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { usePaginatedQuery } from "convex-helpers/react";
|
|
4
|
+
import { useMutation, useQuery } from "convex/react";
|
|
5
|
+
|
|
6
|
+
import type { FeedbackPublicApi } from "../client/api.js";
|
|
7
|
+
import type {
|
|
8
|
+
CommentSort,
|
|
9
|
+
EntryKind,
|
|
10
|
+
EntrySort,
|
|
11
|
+
EntryStatus,
|
|
12
|
+
} from "../component/model.js";
|
|
13
|
+
|
|
14
|
+
export interface FeedbackHooksOptions {
|
|
15
|
+
entryPageSize?: number;
|
|
16
|
+
commentPageSize?: number;
|
|
17
|
+
replyPageSize?: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface UseEntriesArgs {
|
|
21
|
+
kind?: EntryKind;
|
|
22
|
+
status?: EntryStatus;
|
|
23
|
+
sort?: EntrySort;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface UseCommentsArgs {
|
|
27
|
+
entryId: string;
|
|
28
|
+
parentCommentId?: string;
|
|
29
|
+
sort?: CommentSort;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface SearchEntriesArgs {
|
|
33
|
+
searchQuery: string;
|
|
34
|
+
kind?: EntryKind;
|
|
35
|
+
status?: EntryStatus;
|
|
36
|
+
limit?: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface SimilarEntriesArgs {
|
|
40
|
+
title: string;
|
|
41
|
+
body: string;
|
|
42
|
+
kind?: EntryKind;
|
|
43
|
+
limit?: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function positivePageSize(value: number | undefined, fallback: number): number {
|
|
47
|
+
return value !== undefined && Number.isInteger(value) && value > 0
|
|
48
|
+
? value
|
|
49
|
+
: fallback;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function createFeedbackHooks(
|
|
53
|
+
api: FeedbackPublicApi,
|
|
54
|
+
options: FeedbackHooksOptions = {},
|
|
55
|
+
) {
|
|
56
|
+
const entryPageSize = positivePageSize(options.entryPageSize, 20);
|
|
57
|
+
const commentPageSize = positivePageSize(options.commentPageSize, 20);
|
|
58
|
+
const replyPageSize = positivePageSize(options.replyPageSize, 10);
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
pageSizes: {
|
|
62
|
+
entries: entryPageSize,
|
|
63
|
+
comments: commentPageSize,
|
|
64
|
+
replies: replyPageSize,
|
|
65
|
+
} as const,
|
|
66
|
+
|
|
67
|
+
useEntries(args: UseEntriesArgs = {}) {
|
|
68
|
+
return usePaginatedQuery(api.listEntries, args, {
|
|
69
|
+
initialNumItems: entryPageSize,
|
|
70
|
+
});
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
useEntry(entryId: string | null | undefined) {
|
|
74
|
+
return useQuery(
|
|
75
|
+
api.getEntry,
|
|
76
|
+
entryId === null || entryId === undefined ? "skip" : { entryId },
|
|
77
|
+
);
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
useSearchEntries(args: SearchEntriesArgs) {
|
|
81
|
+
const searchQuery = args.searchQuery.trim();
|
|
82
|
+
const result = useQuery(
|
|
83
|
+
api.searchEntries,
|
|
84
|
+
searchQuery.length === 0 ? "skip" : { ...args, searchQuery },
|
|
85
|
+
);
|
|
86
|
+
return searchQuery.length === 0 ? [] : result;
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
useSimilarEntries(args: SimilarEntriesArgs) {
|
|
90
|
+
const shouldSkip =
|
|
91
|
+
args.title.trim().length === 0 && args.body.trim().length === 0;
|
|
92
|
+
const result = useQuery(
|
|
93
|
+
api.findSimilarEntries,
|
|
94
|
+
shouldSkip ? "skip" : args,
|
|
95
|
+
);
|
|
96
|
+
return shouldSkip ? { exact: [], similar: [] } : result;
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
useComments(args: UseCommentsArgs) {
|
|
100
|
+
return usePaginatedQuery(api.listComments, args, {
|
|
101
|
+
initialNumItems:
|
|
102
|
+
args.parentCommentId === undefined ? commentPageSize : replyPageSize,
|
|
103
|
+
});
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
useCreateEntry() {
|
|
107
|
+
return useMutation(api.createEntry);
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
useUpdateEntry() {
|
|
111
|
+
return useMutation(api.updateEntry);
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
useSetEntryStatus() {
|
|
115
|
+
return useMutation(api.setEntryStatus);
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
useSetEntryUpvote() {
|
|
119
|
+
return useMutation(api.setEntryUpvote);
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
useCreateComment() {
|
|
123
|
+
return useMutation(api.createComment);
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
useUpdateComment() {
|
|
127
|
+
return useMutation(api.updateComment);
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
useDeleteComment() {
|
|
131
|
+
return useMutation(api.deleteComment);
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
useSetCommentLike() {
|
|
135
|
+
return useMutation(api.setCommentLike);
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export type FeedbackHooks = ReturnType<typeof createFeedbackHooks>;
|
package/src/test.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
2
|
+
|
|
3
|
+
import type { TestConvex } from "convex-test";
|
|
4
|
+
import type { GenericSchema, SchemaDefinition } from "convex/server";
|
|
5
|
+
|
|
6
|
+
import schema from "./component/schema.js";
|
|
7
|
+
|
|
8
|
+
const modules = import.meta.glob("./component/**/*.ts");
|
|
9
|
+
|
|
10
|
+
export function register(
|
|
11
|
+
test: TestConvex<SchemaDefinition<GenericSchema, boolean>>,
|
|
12
|
+
name = "feedback",
|
|
13
|
+
): void {
|
|
14
|
+
test.registerComponent(name, schema, modules);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export { schema, modules };
|
|
18
|
+
export default { register, schema, modules };
|