@btst/stack 2.1.0 → 2.3.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/dist/api/index.cjs +9 -1
- package/dist/api/index.d.cts +4 -4
- package/dist/api/index.d.mts +4 -4
- package/dist/api/index.d.ts +4 -4
- package/dist/api/index.mjs +9 -1
- package/dist/client/index.d.cts +2 -2
- package/dist/client/index.d.mts +2 -2
- package/dist/client/index.d.ts +2 -2
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/packages/stack/src/plugins/ai-chat/api/getters.cjs +42 -0
- package/dist/packages/stack/src/plugins/ai-chat/api/getters.mjs +39 -0
- package/dist/packages/stack/src/plugins/ai-chat/api/plugin.cjs +5 -0
- package/dist/packages/stack/src/plugins/ai-chat/api/plugin.mjs +5 -0
- package/dist/packages/stack/src/plugins/blog/api/getters.cjs +131 -0
- package/dist/packages/stack/src/plugins/blog/api/getters.mjs +127 -0
- package/dist/packages/stack/src/plugins/blog/api/plugin.cjs +60 -107
- package/dist/packages/stack/src/plugins/blog/api/plugin.mjs +60 -107
- package/dist/packages/stack/src/plugins/blog/api/query-key-defs.cjs +18 -0
- package/dist/packages/stack/src/plugins/blog/api/query-key-defs.mjs +15 -0
- package/dist/packages/stack/src/plugins/blog/api/serializers.cjs +21 -0
- package/dist/packages/stack/src/plugins/blog/api/serializers.mjs +18 -0
- package/dist/packages/stack/src/plugins/blog/client/plugin.cjs +16 -1
- package/dist/packages/stack/src/plugins/blog/client/plugin.mjs +17 -2
- package/dist/packages/stack/src/plugins/cms/api/getters.cjs +156 -0
- package/dist/packages/stack/src/plugins/cms/api/getters.mjs +147 -0
- package/dist/packages/stack/src/plugins/cms/api/plugin.cjs +624 -617
- package/dist/packages/stack/src/plugins/cms/api/plugin.mjs +623 -616
- package/dist/packages/stack/src/plugins/cms/api/query-key-defs.cjs +29 -0
- package/dist/packages/stack/src/plugins/cms/api/query-key-defs.mjs +26 -0
- package/dist/packages/stack/src/plugins/cms/client/components/pages/content-editor-page.internal.cjs +1 -1
- package/dist/packages/stack/src/plugins/cms/client/components/pages/content-editor-page.internal.mjs +1 -1
- package/dist/packages/stack/src/plugins/cms/client/hooks/cms-hooks.cjs +6 -3
- package/dist/packages/stack/src/plugins/cms/client/hooks/cms-hooks.mjs +6 -3
- package/dist/packages/stack/src/plugins/cms/client/plugin.cjs +15 -0
- package/dist/packages/stack/src/plugins/cms/client/plugin.mjs +16 -1
- package/dist/packages/stack/src/plugins/form-builder/api/getters.cjs +120 -0
- package/dist/packages/stack/src/plugins/form-builder/api/getters.mjs +112 -0
- package/dist/packages/stack/src/plugins/form-builder/api/plugin.cjs +75 -86
- package/dist/packages/stack/src/plugins/form-builder/api/plugin.mjs +71 -82
- package/dist/packages/stack/src/plugins/form-builder/api/query-key-defs.cjs +37 -0
- package/dist/packages/stack/src/plugins/form-builder/api/query-key-defs.mjs +33 -0
- package/dist/packages/stack/src/plugins/form-builder/client/components/pages/submissions-page.internal.cjs +1 -1
- package/dist/packages/stack/src/plugins/form-builder/client/components/pages/submissions-page.internal.mjs +1 -1
- package/dist/packages/stack/src/plugins/form-builder/client/plugin.cjs +15 -0
- package/dist/packages/stack/src/plugins/form-builder/client/plugin.mjs +16 -1
- package/dist/packages/stack/src/plugins/kanban/api/getters.cjs +84 -0
- package/dist/packages/stack/src/plugins/kanban/api/getters.mjs +81 -0
- package/dist/packages/stack/src/plugins/kanban/api/plugin.cjs +37 -123
- package/dist/packages/stack/src/plugins/kanban/api/plugin.mjs +37 -123
- package/dist/packages/stack/src/plugins/kanban/api/query-key-defs.cjs +26 -0
- package/dist/packages/stack/src/plugins/kanban/api/query-key-defs.mjs +23 -0
- package/dist/packages/stack/src/plugins/kanban/api/serializers.cjs +30 -0
- package/dist/packages/stack/src/plugins/kanban/api/serializers.mjs +26 -0
- package/dist/packages/stack/src/plugins/kanban/client/plugin.cjs +11 -1
- package/dist/packages/stack/src/plugins/kanban/client/plugin.mjs +12 -2
- package/dist/packages/stack/src/plugins/utils.cjs +6 -0
- package/dist/packages/stack/src/plugins/utils.mjs +6 -1
- package/dist/plugins/ai-chat/api/index.cjs +3 -0
- package/dist/plugins/ai-chat/api/index.d.cts +27 -4
- package/dist/plugins/ai-chat/api/index.d.mts +27 -4
- package/dist/plugins/ai-chat/api/index.d.ts +27 -4
- package/dist/plugins/ai-chat/api/index.mjs +1 -0
- package/dist/plugins/ai-chat/client/hooks/index.d.cts +2 -2
- package/dist/plugins/ai-chat/client/hooks/index.d.mts +2 -2
- package/dist/plugins/ai-chat/client/hooks/index.d.ts +2 -2
- package/dist/plugins/ai-chat/query-keys.d.cts +9 -284
- package/dist/plugins/ai-chat/query-keys.d.mts +9 -284
- package/dist/plugins/ai-chat/query-keys.d.ts +9 -284
- package/dist/plugins/api/index.d.cts +4 -3
- package/dist/plugins/api/index.d.mts +4 -3
- package/dist/plugins/api/index.d.ts +4 -3
- package/dist/plugins/blog/api/index.cjs +9 -0
- package/dist/plugins/blog/api/index.d.cts +20 -4
- package/dist/plugins/blog/api/index.d.mts +20 -4
- package/dist/plugins/blog/api/index.d.ts +20 -4
- package/dist/plugins/blog/api/index.mjs +3 -0
- package/dist/plugins/blog/client/hooks/index.d.cts +5 -5
- package/dist/plugins/blog/client/hooks/index.d.mts +5 -5
- package/dist/plugins/blog/client/hooks/index.d.ts +5 -5
- package/dist/plugins/blog/client/index.d.cts +1 -1
- package/dist/plugins/blog/client/index.d.mts +1 -1
- package/dist/plugins/blog/client/index.d.ts +1 -1
- package/dist/plugins/blog/query-keys.cjs +13 -9
- package/dist/plugins/blog/query-keys.d.cts +8 -333
- package/dist/plugins/blog/query-keys.d.mts +8 -333
- package/dist/plugins/blog/query-keys.d.ts +8 -333
- package/dist/plugins/blog/query-keys.mjs +13 -9
- package/dist/plugins/client/index.cjs +1 -0
- package/dist/plugins/client/index.d.cts +10 -3
- package/dist/plugins/client/index.d.mts +10 -3
- package/dist/plugins/client/index.d.ts +10 -3
- package/dist/plugins/client/index.mjs +1 -1
- package/dist/plugins/cms/api/index.cjs +10 -0
- package/dist/plugins/cms/api/index.d.cts +7 -163
- package/dist/plugins/cms/api/index.d.mts +7 -163
- package/dist/plugins/cms/api/index.d.ts +7 -163
- package/dist/plugins/cms/api/index.mjs +2 -0
- package/dist/plugins/cms/client/hooks/index.d.cts +1 -1
- package/dist/plugins/cms/client/hooks/index.d.mts +1 -1
- package/dist/plugins/cms/client/hooks/index.d.ts +1 -1
- package/dist/plugins/cms/query-keys.cjs +2 -1
- package/dist/plugins/cms/query-keys.d.cts +6 -9
- package/dist/plugins/cms/query-keys.d.mts +6 -9
- package/dist/plugins/cms/query-keys.d.ts +6 -9
- package/dist/plugins/cms/query-keys.mjs +2 -1
- package/dist/plugins/form-builder/api/index.cjs +10 -0
- package/dist/plugins/form-builder/api/index.d.cts +7 -141
- package/dist/plugins/form-builder/api/index.d.mts +7 -141
- package/dist/plugins/form-builder/api/index.d.ts +7 -141
- package/dist/plugins/form-builder/api/index.mjs +2 -0
- package/dist/plugins/form-builder/client/components/index.d.cts +1 -1
- package/dist/plugins/form-builder/client/components/index.d.mts +1 -1
- package/dist/plugins/form-builder/client/components/index.d.ts +1 -1
- package/dist/plugins/form-builder/client/hooks/index.d.cts +1 -1
- package/dist/plugins/form-builder/client/hooks/index.d.mts +1 -1
- package/dist/plugins/form-builder/client/hooks/index.d.ts +1 -1
- package/dist/plugins/form-builder/query-keys.cjs +3 -2
- package/dist/plugins/form-builder/query-keys.d.cts +7 -6
- package/dist/plugins/form-builder/query-keys.d.mts +7 -6
- package/dist/plugins/form-builder/query-keys.d.ts +7 -6
- package/dist/plugins/form-builder/query-keys.mjs +3 -2
- package/dist/plugins/kanban/api/index.cjs +9 -0
- package/dist/plugins/kanban/api/index.d.cts +17 -395
- package/dist/plugins/kanban/api/index.d.mts +17 -395
- package/dist/plugins/kanban/api/index.d.ts +17 -395
- package/dist/plugins/kanban/api/index.mjs +3 -0
- package/dist/plugins/kanban/client/components/index.d.cts +1 -1
- package/dist/plugins/kanban/client/components/index.d.mts +1 -1
- package/dist/plugins/kanban/client/components/index.d.ts +1 -1
- package/dist/plugins/kanban/client/hooks/index.d.cts +1 -1
- package/dist/plugins/kanban/client/hooks/index.d.mts +1 -1
- package/dist/plugins/kanban/client/hooks/index.d.ts +1 -1
- package/dist/plugins/kanban/client/index.d.cts +1 -1
- package/dist/plugins/kanban/client/index.d.mts +1 -1
- package/dist/plugins/kanban/client/index.d.ts +1 -1
- package/dist/plugins/kanban/query-keys.cjs +6 -12
- package/dist/plugins/kanban/query-keys.d.cts +5 -16
- package/dist/plugins/kanban/query-keys.d.mts +5 -16
- package/dist/plugins/kanban/query-keys.d.ts +5 -16
- package/dist/plugins/kanban/query-keys.mjs +6 -12
- package/dist/plugins/open-api/api/index.d.cts +2 -2
- package/dist/plugins/open-api/api/index.d.mts +2 -2
- package/dist/plugins/open-api/api/index.d.ts +2 -2
- package/dist/plugins/route-docs/client/index.d.cts +1 -1
- package/dist/plugins/route-docs/client/index.d.mts +1 -1
- package/dist/plugins/route-docs/client/index.d.ts +1 -1
- package/dist/plugins/ui-builder/index.d.cts +1 -1
- package/dist/plugins/ui-builder/index.d.mts +1 -1
- package/dist/plugins/ui-builder/index.d.ts +1 -1
- package/dist/shared/{stack.BoA0xkJv.d.cts → stack.7n9Y_u7N.d.cts} +33 -7
- package/dist/shared/{stack.BoA0xkJv.d.mts → stack.7n9Y_u7N.d.mts} +33 -7
- package/dist/shared/{stack.BoA0xkJv.d.ts → stack.7n9Y_u7N.d.ts} +33 -7
- package/dist/shared/stack.B1EeBt1b.d.ts +297 -0
- package/dist/shared/stack.BIXEI6v_.d.mts +419 -0
- package/dist/shared/stack.BKfolAyK.d.ts +419 -0
- package/dist/shared/stack.BeSm90va.d.ts +289 -0
- package/dist/shared/stack.BpolpQpf.d.cts +445 -0
- package/dist/shared/stack.C5dtIncc.d.mts +293 -0
- package/dist/shared/stack.CIP6QS9l.d.ts +293 -0
- package/dist/shared/stack.CMh_EdxW.d.cts +289 -0
- package/dist/shared/stack.CP68pFEH.d.mts +297 -0
- package/dist/shared/{stack.BsXokfNh.d.mts → stack.CVDTkMoO.d.cts} +8 -2
- package/dist/shared/{stack.BsXokfNh.d.ts → stack.CVDTkMoO.d.mts} +8 -2
- package/dist/shared/{stack.BsXokfNh.d.cts → stack.CVDTkMoO.d.ts} +8 -2
- package/dist/shared/{stack.DKDMI-QO.d.mts → stack.DJaKVY7v.d.cts} +7 -1
- package/dist/shared/{stack.DKDMI-QO.d.ts → stack.DJaKVY7v.d.mts} +7 -1
- package/dist/shared/{stack.DKDMI-QO.d.cts → stack.DJaKVY7v.d.ts} +7 -1
- package/dist/shared/{stack.DzH_wcvr.d.mts → stack.DdI5W6MB.d.cts} +9 -3
- package/dist/shared/{stack.DzH_wcvr.d.ts → stack.DdI5W6MB.d.mts} +9 -3
- package/dist/shared/{stack.DzH_wcvr.d.cts → stack.DdI5W6MB.d.ts} +9 -3
- package/dist/shared/stack.Dg09R0oB.d.mts +289 -0
- package/dist/shared/stack.Dw0Ly2TM.d.cts +293 -0
- package/dist/shared/stack.IdtKDRka.d.cts +297 -0
- package/dist/shared/stack.TIBF2AOx.d.ts +445 -0
- package/dist/shared/stack.rTy7-wQU.d.mts +445 -0
- package/dist/shared/stack.snB1EDP7.d.cts +419 -0
- package/package.json +3 -3
- package/src/__tests__/stack-api.test.ts +118 -0
- package/src/api/index.ts +15 -1
- package/src/plugins/ai-chat/__tests__/getters.test.ts +109 -0
- package/src/plugins/ai-chat/api/getters.ts +71 -0
- package/src/plugins/ai-chat/api/index.ts +1 -0
- package/src/plugins/ai-chat/api/plugin.ts +8 -0
- package/src/plugins/api/index.ts +3 -1
- package/src/plugins/blog/__tests__/getters.test.ts +540 -0
- package/src/plugins/blog/api/getters.ts +243 -0
- package/src/plugins/blog/api/index.ts +9 -0
- package/src/plugins/blog/api/plugin.ts +98 -141
- package/src/plugins/blog/api/query-key-defs.ts +46 -0
- package/src/plugins/blog/api/serializers.ts +27 -0
- package/src/plugins/blog/client/plugin.tsx +21 -1
- package/src/plugins/blog/query-keys.ts +21 -20
- package/src/plugins/client/index.ts +1 -1
- package/src/plugins/cms/__tests__/getters.test.ts +206 -0
- package/src/plugins/cms/api/getters.ts +268 -0
- package/src/plugins/cms/api/index.ts +15 -1
- package/src/plugins/cms/api/plugin.ts +151 -150
- package/src/plugins/cms/api/query-key-defs.ts +53 -0
- package/src/plugins/cms/api/serializers.ts +12 -0
- package/src/plugins/cms/client/components/pages/content-editor-page.internal.tsx +1 -1
- package/src/plugins/cms/client/hooks/cms-hooks.tsx +3 -0
- package/src/plugins/cms/client/plugin.tsx +19 -0
- package/src/plugins/cms/query-keys.ts +2 -1
- package/src/plugins/cms/types.ts +1 -1
- package/src/plugins/form-builder/__tests__/getters.test.ts +159 -0
- package/src/plugins/form-builder/api/getters.ts +226 -0
- package/src/plugins/form-builder/api/index.ts +15 -1
- package/src/plugins/form-builder/api/plugin.ts +107 -109
- package/src/plugins/form-builder/api/query-key-defs.ts +79 -0
- package/src/plugins/form-builder/api/serializers.ts +12 -0
- package/src/plugins/form-builder/client/components/pages/submissions-page.internal.tsx +1 -1
- package/src/plugins/form-builder/client/plugin.tsx +19 -0
- package/src/plugins/form-builder/query-keys.ts +6 -2
- package/src/plugins/form-builder/types.ts +2 -2
- package/src/plugins/kanban/__tests__/getters.test.ts +172 -0
- package/src/plugins/kanban/api/getters.ts +149 -0
- package/src/plugins/kanban/api/index.ts +4 -0
- package/src/plugins/kanban/api/plugin.ts +65 -146
- package/src/plugins/kanban/api/query-key-defs.ts +54 -0
- package/src/plugins/kanban/api/serializers.ts +49 -0
- package/src/plugins/kanban/client/plugin.tsx +15 -1
- package/src/plugins/kanban/query-keys.ts +10 -14
- package/src/plugins/utils.ts +19 -0
- package/src/types.ts +44 -5
- package/dist/shared/{stack.CbuN2zVV.d.cts → stack.CBON0dWL.d.cts} +7 -7
- package/dist/shared/{stack.CbuN2zVV.d.mts → stack.CBON0dWL.d.mts} +7 -7
- package/dist/shared/{stack.CbuN2zVV.d.ts → stack.CBON0dWL.d.ts} +7 -7
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import type { Adapter } from "@btst/db";
|
|
2
|
+
import type {
|
|
3
|
+
Form,
|
|
4
|
+
FormSubmission,
|
|
5
|
+
FormSubmissionWithForm,
|
|
6
|
+
SerializedForm,
|
|
7
|
+
SerializedFormSubmission,
|
|
8
|
+
SerializedFormSubmissionWithData,
|
|
9
|
+
} from "../types";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Serialize a Form for SSR/SSG use (convert dates to strings).
|
|
13
|
+
*/
|
|
14
|
+
export function serializeForm(form: Form): SerializedForm {
|
|
15
|
+
return {
|
|
16
|
+
id: form.id,
|
|
17
|
+
name: form.name,
|
|
18
|
+
slug: form.slug,
|
|
19
|
+
description: form.description,
|
|
20
|
+
schema: form.schema,
|
|
21
|
+
successMessage: form.successMessage,
|
|
22
|
+
redirectUrl: form.redirectUrl,
|
|
23
|
+
status: form.status,
|
|
24
|
+
createdBy: form.createdBy,
|
|
25
|
+
createdAt: form.createdAt.toISOString(),
|
|
26
|
+
updatedAt: form.updatedAt.toISOString(),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Serialize a FormSubmission for SSR/SSG use (convert dates to strings).
|
|
32
|
+
*/
|
|
33
|
+
export function serializeFormSubmission(
|
|
34
|
+
submission: FormSubmission,
|
|
35
|
+
): SerializedFormSubmission {
|
|
36
|
+
return {
|
|
37
|
+
...submission,
|
|
38
|
+
submittedAt: submission.submittedAt.toISOString(),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Serialize a FormSubmission with parsed data and joined Form.
|
|
44
|
+
* If `submission.data` is corrupted JSON, `parsedData` is set to `null` rather
|
|
45
|
+
* than throwing, so one bad row cannot crash the entire list or SSG build.
|
|
46
|
+
*/
|
|
47
|
+
export function serializeFormSubmissionWithData(
|
|
48
|
+
submission: FormSubmissionWithForm,
|
|
49
|
+
): SerializedFormSubmissionWithData {
|
|
50
|
+
let parsedData: Record<string, unknown> | null = null;
|
|
51
|
+
try {
|
|
52
|
+
parsedData = JSON.parse(submission.data);
|
|
53
|
+
} catch {
|
|
54
|
+
// Corrupted JSON — leave parsedData as null so callers can handle it
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
...serializeFormSubmission(submission),
|
|
58
|
+
parsedData,
|
|
59
|
+
form: submission.form ? serializeForm(submission.form) : undefined,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Retrieve all forms with optional status filter and pagination.
|
|
65
|
+
* Pure DB function — no hooks, no HTTP context. Safe for SSG and server-side use.
|
|
66
|
+
*
|
|
67
|
+
* @remarks **Security:** Authorization hooks (e.g. `onBeforeListForms`) are NOT
|
|
68
|
+
* called. The caller is responsible for any access-control checks before
|
|
69
|
+
* invoking this function.
|
|
70
|
+
*
|
|
71
|
+
* @param adapter - The database adapter
|
|
72
|
+
* @param params - Optional filter/pagination parameters
|
|
73
|
+
*/
|
|
74
|
+
export async function getAllForms(
|
|
75
|
+
adapter: Adapter,
|
|
76
|
+
params?: { status?: string; limit?: number; offset?: number },
|
|
77
|
+
): Promise<{
|
|
78
|
+
items: SerializedForm[];
|
|
79
|
+
total: number;
|
|
80
|
+
limit?: number;
|
|
81
|
+
offset?: number;
|
|
82
|
+
}> {
|
|
83
|
+
const whereConditions: Array<{
|
|
84
|
+
field: string;
|
|
85
|
+
value: string;
|
|
86
|
+
operator: "eq";
|
|
87
|
+
}> = [];
|
|
88
|
+
|
|
89
|
+
if (params?.status) {
|
|
90
|
+
whereConditions.push({
|
|
91
|
+
field: "status",
|
|
92
|
+
value: params.status,
|
|
93
|
+
operator: "eq" as const,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// TODO: remove cast once @btst/db types expose adapter.count()
|
|
98
|
+
const total: number = await adapter.count({
|
|
99
|
+
model: "form",
|
|
100
|
+
where: whereConditions.length > 0 ? whereConditions : undefined,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const forms = await adapter.findMany<Form>({
|
|
104
|
+
model: "form",
|
|
105
|
+
where: whereConditions.length > 0 ? whereConditions : undefined,
|
|
106
|
+
limit: params?.limit,
|
|
107
|
+
offset: params?.offset,
|
|
108
|
+
sortBy: { field: "createdAt", direction: "desc" },
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
items: forms.map(serializeForm),
|
|
113
|
+
total,
|
|
114
|
+
limit: params?.limit,
|
|
115
|
+
offset: params?.offset,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Retrieve a single form by its ID (UUID).
|
|
121
|
+
* Returns null if the form is not found.
|
|
122
|
+
* Pure DB function — no hooks, no HTTP context. Safe for SSG and server-side use.
|
|
123
|
+
*
|
|
124
|
+
* @remarks **Security:** Authorization hooks are NOT called. The caller is
|
|
125
|
+
* responsible for any access-control checks before invoking this function.
|
|
126
|
+
*
|
|
127
|
+
* @param adapter - The database adapter
|
|
128
|
+
* @param id - The form UUID
|
|
129
|
+
*/
|
|
130
|
+
export async function getFormById(
|
|
131
|
+
adapter: Adapter,
|
|
132
|
+
id: string,
|
|
133
|
+
): Promise<SerializedForm | null> {
|
|
134
|
+
const form = await adapter.findOne<Form>({
|
|
135
|
+
model: "form",
|
|
136
|
+
where: [{ field: "id", value: id, operator: "eq" as const }],
|
|
137
|
+
});
|
|
138
|
+
if (!form) return null;
|
|
139
|
+
return serializeForm(form);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Retrieve a single form by its slug.
|
|
144
|
+
* Returns null if the form is not found.
|
|
145
|
+
* Pure DB function — no hooks, no HTTP context. Safe for SSG and server-side use.
|
|
146
|
+
*
|
|
147
|
+
* @remarks **Security:** Authorization hooks are NOT called. The caller is
|
|
148
|
+
* responsible for any access-control checks before invoking this function.
|
|
149
|
+
*
|
|
150
|
+
* @param adapter - The database adapter
|
|
151
|
+
* @param slug - The form slug
|
|
152
|
+
*/
|
|
153
|
+
export async function getFormBySlug(
|
|
154
|
+
adapter: Adapter,
|
|
155
|
+
slug: string,
|
|
156
|
+
): Promise<SerializedForm | null> {
|
|
157
|
+
const form = await adapter.findOne<Form>({
|
|
158
|
+
model: "form",
|
|
159
|
+
where: [{ field: "slug", value: slug, operator: "eq" as const }],
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
if (!form) {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return serializeForm(form);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Retrieve submissions for a form by form ID, with optional pagination.
|
|
171
|
+
* Returns an empty result if the form does not exist.
|
|
172
|
+
* Pure DB function — no hooks, no HTTP context. Safe for server-side use.
|
|
173
|
+
*
|
|
174
|
+
* @remarks **Security:** Authorization hooks are NOT called. The caller is
|
|
175
|
+
* responsible for any access-control checks before invoking this function.
|
|
176
|
+
*
|
|
177
|
+
* @param adapter - The database adapter
|
|
178
|
+
* @param formId - The form ID
|
|
179
|
+
* @param params - Optional pagination parameters
|
|
180
|
+
*/
|
|
181
|
+
export async function getFormSubmissions(
|
|
182
|
+
adapter: Adapter,
|
|
183
|
+
formId: string,
|
|
184
|
+
params?: { limit?: number; offset?: number },
|
|
185
|
+
): Promise<{
|
|
186
|
+
items: SerializedFormSubmissionWithData[];
|
|
187
|
+
total: number;
|
|
188
|
+
limit?: number;
|
|
189
|
+
offset?: number;
|
|
190
|
+
}> {
|
|
191
|
+
const form = await adapter.findOne<Form>({
|
|
192
|
+
model: "form",
|
|
193
|
+
where: [{ field: "id", value: formId, operator: "eq" as const }],
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
if (!form) {
|
|
197
|
+
return {
|
|
198
|
+
items: [],
|
|
199
|
+
total: 0,
|
|
200
|
+
limit: params?.limit,
|
|
201
|
+
offset: params?.offset,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// TODO: remove cast once @btst/db types expose adapter.count()
|
|
206
|
+
const total: number = await adapter.count({
|
|
207
|
+
model: "formSubmission",
|
|
208
|
+
where: [{ field: "formId", value: formId, operator: "eq" as const }],
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
const submissions = await adapter.findMany<FormSubmissionWithForm>({
|
|
212
|
+
model: "formSubmission",
|
|
213
|
+
where: [{ field: "formId", value: formId, operator: "eq" as const }],
|
|
214
|
+
limit: params?.limit,
|
|
215
|
+
offset: params?.offset,
|
|
216
|
+
sortBy: { field: "submittedAt", direction: "desc" },
|
|
217
|
+
join: { form: true },
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
items: submissions.map(serializeFormSubmissionWithData),
|
|
222
|
+
total,
|
|
223
|
+
limit: params?.limit,
|
|
224
|
+
offset: params?.offset,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
@@ -1 +1,15 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export {
|
|
2
|
+
formBuilderBackendPlugin,
|
|
3
|
+
type FormBuilderApiRouter,
|
|
4
|
+
type FormBuilderRouteKey,
|
|
5
|
+
} from "./plugin";
|
|
6
|
+
export {
|
|
7
|
+
getAllForms,
|
|
8
|
+
getFormById,
|
|
9
|
+
getFormBySlug,
|
|
10
|
+
getFormSubmissions,
|
|
11
|
+
serializeForm,
|
|
12
|
+
serializeFormSubmission,
|
|
13
|
+
serializeFormSubmissionWithData,
|
|
14
|
+
} from "./getters";
|
|
15
|
+
export { FORM_QUERY_KEYS } from "./query-key-defs";
|
|
@@ -11,9 +11,6 @@ import type {
|
|
|
11
11
|
FormBuilderBackendConfig,
|
|
12
12
|
FormBuilderHookContext,
|
|
13
13
|
SubmissionHookContext,
|
|
14
|
-
SerializedForm,
|
|
15
|
-
SerializedFormSubmission,
|
|
16
|
-
SerializedFormSubmissionWithData,
|
|
17
14
|
FormInput,
|
|
18
15
|
FormUpdate,
|
|
19
16
|
} from "../types";
|
|
@@ -24,49 +21,102 @@ import {
|
|
|
24
21
|
listSubmissionsQuerySchema,
|
|
25
22
|
} from "../schemas";
|
|
26
23
|
import { slugify, extractIpAddress, extractUserAgent } from "../utils";
|
|
24
|
+
import {
|
|
25
|
+
getAllForms,
|
|
26
|
+
getFormById as getFormByIdFromDb,
|
|
27
|
+
getFormBySlug as getFormBySlugFromDb,
|
|
28
|
+
getFormSubmissions,
|
|
29
|
+
serializeForm,
|
|
30
|
+
serializeFormSubmission,
|
|
31
|
+
serializeFormSubmissionWithData,
|
|
32
|
+
} from "./getters";
|
|
33
|
+
import { FORM_QUERY_KEYS } from "./query-key-defs";
|
|
34
|
+
import type { QueryClient } from "@tanstack/react-query";
|
|
27
35
|
|
|
28
36
|
/**
|
|
29
|
-
*
|
|
30
|
-
|
|
31
|
-
function serializeForm(form: Form): SerializedForm {
|
|
32
|
-
return {
|
|
33
|
-
id: form.id,
|
|
34
|
-
name: form.name,
|
|
35
|
-
slug: form.slug,
|
|
36
|
-
description: form.description,
|
|
37
|
-
schema: form.schema,
|
|
38
|
-
successMessage: form.successMessage,
|
|
39
|
-
redirectUrl: form.redirectUrl,
|
|
40
|
-
status: form.status,
|
|
41
|
-
createdBy: form.createdBy,
|
|
42
|
-
createdAt: form.createdAt.toISOString(),
|
|
43
|
-
updatedAt: form.updatedAt.toISOString(),
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Serialize a FormSubmission for API response (convert dates to strings)
|
|
37
|
+
* Route keys for the Form Builder plugin — matches the keys returned by
|
|
38
|
+
* `stackClient.router.getRoute(path).routeKey`.
|
|
49
39
|
*/
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
40
|
+
export type FormBuilderRouteKey =
|
|
41
|
+
| "formList"
|
|
42
|
+
| "newForm"
|
|
43
|
+
| "editForm"
|
|
44
|
+
| "submissions";
|
|
45
|
+
|
|
46
|
+
interface FormBuilderPrefetchForRoute {
|
|
47
|
+
(key: "formList" | "newForm", qc: QueryClient): Promise<void>;
|
|
48
|
+
(
|
|
49
|
+
key: "editForm" | "submissions",
|
|
50
|
+
qc: QueryClient,
|
|
51
|
+
params: { id: string },
|
|
52
|
+
): Promise<void>;
|
|
57
53
|
}
|
|
58
54
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
function
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
55
|
+
function createFormBuilderPrefetchForRoute(
|
|
56
|
+
adapter: Parameters<typeof getAllForms>[0],
|
|
57
|
+
): FormBuilderPrefetchForRoute {
|
|
58
|
+
return async function prefetchForRoute(
|
|
59
|
+
key: FormBuilderRouteKey,
|
|
60
|
+
qc: QueryClient,
|
|
61
|
+
params?: Record<string, string>,
|
|
62
|
+
): Promise<void> {
|
|
63
|
+
switch (key) {
|
|
64
|
+
case "formList": {
|
|
65
|
+
const result = await getAllForms(adapter, { limit: 20, offset: 0 });
|
|
66
|
+
qc.setQueryData(FORM_QUERY_KEYS.formsList({ limit: 20, offset: 0 }), {
|
|
67
|
+
pages: [
|
|
68
|
+
{
|
|
69
|
+
items: result.items,
|
|
70
|
+
total: result.total,
|
|
71
|
+
limit: result.limit ?? 20,
|
|
72
|
+
offset: result.offset ?? 0,
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
pageParams: [0],
|
|
76
|
+
});
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
case "editForm": {
|
|
80
|
+
const id = params?.id ?? "";
|
|
81
|
+
if (id) {
|
|
82
|
+
const form = await getFormByIdFromDb(adapter, id);
|
|
83
|
+
qc.setQueryData(FORM_QUERY_KEYS.formById(id), form);
|
|
84
|
+
}
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
case "submissions": {
|
|
88
|
+
const id = params?.id ?? "";
|
|
89
|
+
if (id) {
|
|
90
|
+
const [form, submissionsResult] = await Promise.all([
|
|
91
|
+
getFormByIdFromDb(adapter, id),
|
|
92
|
+
getFormSubmissions(adapter, id, { limit: 20, offset: 0 }),
|
|
93
|
+
]);
|
|
94
|
+
qc.setQueryData(FORM_QUERY_KEYS.formById(id), form);
|
|
95
|
+
qc.setQueryData(
|
|
96
|
+
FORM_QUERY_KEYS.submissionsList({
|
|
97
|
+
formId: id,
|
|
98
|
+
limit: 20,
|
|
99
|
+
offset: 0,
|
|
100
|
+
}),
|
|
101
|
+
{
|
|
102
|
+
pages: [
|
|
103
|
+
{
|
|
104
|
+
items: submissionsResult.items,
|
|
105
|
+
total: submissionsResult.total,
|
|
106
|
+
limit: submissionsResult.limit ?? 20,
|
|
107
|
+
offset: submissionsResult.offset ?? 0,
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
pageParams: [0],
|
|
111
|
+
},
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
default:
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
} as FormBuilderPrefetchForRoute;
|
|
70
120
|
}
|
|
71
121
|
|
|
72
122
|
/**
|
|
@@ -83,6 +133,18 @@ export const formBuilderBackendPlugin = (
|
|
|
83
133
|
|
|
84
134
|
dbPlugin: dbSchema,
|
|
85
135
|
|
|
136
|
+
api: (adapter) => ({
|
|
137
|
+
getAllForms: (params?: Parameters<typeof getAllForms>[1]) =>
|
|
138
|
+
getAllForms(adapter, params),
|
|
139
|
+
getFormById: (id: string) => getFormByIdFromDb(adapter, id),
|
|
140
|
+
getFormBySlug: (slug: string) => getFormBySlugFromDb(adapter, slug),
|
|
141
|
+
getFormSubmissions: (
|
|
142
|
+
formId: string,
|
|
143
|
+
params?: Parameters<typeof getFormSubmissions>[2],
|
|
144
|
+
) => getFormSubmissions(adapter, formId, params),
|
|
145
|
+
prefetchForRoute: createFormBuilderPrefetchForRoute(adapter),
|
|
146
|
+
}),
|
|
147
|
+
|
|
86
148
|
routes: (adapter: Adapter) => {
|
|
87
149
|
// Helper to create hook context from request
|
|
88
150
|
const createContext = (headers?: Headers): FormBuilderHookContext => ({
|
|
@@ -114,7 +176,6 @@ export const formBuilderBackendPlugin = (
|
|
|
114
176
|
const { status, limit, offset } = ctx.query;
|
|
115
177
|
const context = createContext(ctx.headers);
|
|
116
178
|
|
|
117
|
-
// Call before hook for auth check
|
|
118
179
|
if (config.hooks?.onBeforeListForms) {
|
|
119
180
|
const canList = await config.hooks.onBeforeListForms(context);
|
|
120
181
|
if (!canList) {
|
|
@@ -122,41 +183,7 @@ export const formBuilderBackendPlugin = (
|
|
|
122
183
|
}
|
|
123
184
|
}
|
|
124
185
|
|
|
125
|
-
|
|
126
|
-
field: string;
|
|
127
|
-
value: string;
|
|
128
|
-
operator: "eq";
|
|
129
|
-
}> = [];
|
|
130
|
-
if (status) {
|
|
131
|
-
whereConditions.push({
|
|
132
|
-
field: "status",
|
|
133
|
-
value: status,
|
|
134
|
-
operator: "eq" as const,
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Get total count
|
|
139
|
-
const allForms = await adapter.findMany<Form>({
|
|
140
|
-
model: "form",
|
|
141
|
-
where: whereConditions.length > 0 ? whereConditions : undefined,
|
|
142
|
-
});
|
|
143
|
-
const total = allForms.length;
|
|
144
|
-
|
|
145
|
-
// Get paginated forms
|
|
146
|
-
const forms = await adapter.findMany<Form>({
|
|
147
|
-
model: "form",
|
|
148
|
-
where: whereConditions.length > 0 ? whereConditions : undefined,
|
|
149
|
-
limit,
|
|
150
|
-
offset,
|
|
151
|
-
sortBy: { field: "createdAt", direction: "desc" },
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
return {
|
|
155
|
-
items: forms.map(serializeForm),
|
|
156
|
-
total,
|
|
157
|
-
limit,
|
|
158
|
-
offset,
|
|
159
|
-
};
|
|
186
|
+
return getAllForms(adapter, { status, limit, offset });
|
|
160
187
|
},
|
|
161
188
|
);
|
|
162
189
|
|
|
@@ -178,16 +205,13 @@ export const formBuilderBackendPlugin = (
|
|
|
178
205
|
}
|
|
179
206
|
}
|
|
180
207
|
|
|
181
|
-
const form = await adapter
|
|
182
|
-
model: "form",
|
|
183
|
-
where: [{ field: "slug", value: slug, operator: "eq" as const }],
|
|
184
|
-
});
|
|
208
|
+
const form = await getFormBySlugFromDb(adapter, slug);
|
|
185
209
|
|
|
186
210
|
if (!form) {
|
|
187
211
|
throw ctx.error(404, { message: "Form not found" });
|
|
188
212
|
}
|
|
189
213
|
|
|
190
|
-
return
|
|
214
|
+
return form;
|
|
191
215
|
},
|
|
192
216
|
);
|
|
193
217
|
|
|
@@ -647,33 +671,7 @@ export const formBuilderBackendPlugin = (
|
|
|
647
671
|
}
|
|
648
672
|
}
|
|
649
673
|
|
|
650
|
-
|
|
651
|
-
const allSubmissions = await adapter.findMany<FormSubmission>({
|
|
652
|
-
model: "formSubmission",
|
|
653
|
-
where: [
|
|
654
|
-
{ field: "formId", value: formId, operator: "eq" as const },
|
|
655
|
-
],
|
|
656
|
-
});
|
|
657
|
-
const total = allSubmissions.length;
|
|
658
|
-
|
|
659
|
-
// Get paginated submissions
|
|
660
|
-
const submissions = await adapter.findMany<FormSubmissionWithForm>({
|
|
661
|
-
model: "formSubmission",
|
|
662
|
-
where: [
|
|
663
|
-
{ field: "formId", value: formId, operator: "eq" as const },
|
|
664
|
-
],
|
|
665
|
-
limit,
|
|
666
|
-
offset,
|
|
667
|
-
sortBy: { field: "submittedAt", direction: "desc" },
|
|
668
|
-
join: { form: true },
|
|
669
|
-
});
|
|
670
|
-
|
|
671
|
-
return {
|
|
672
|
-
items: submissions.map(serializeFormSubmissionWithData),
|
|
673
|
-
total,
|
|
674
|
-
limit,
|
|
675
|
-
offset,
|
|
676
|
-
};
|
|
674
|
+
return getFormSubmissions(adapter, formId, { limit, offset });
|
|
677
675
|
},
|
|
678
676
|
);
|
|
679
677
|
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal query key constants for the Form Builder plugin.
|
|
3
|
+
* Shared between query-keys.ts (HTTP path) and prefetchForRoute (DB path)
|
|
4
|
+
* to prevent key drift between SSR loaders and SSG prefetching.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export interface FormsListDiscriminator {
|
|
8
|
+
status?: "active" | "inactive" | "archived";
|
|
9
|
+
limit: number;
|
|
10
|
+
offset: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface SubmissionsListDiscriminator {
|
|
14
|
+
formId: string;
|
|
15
|
+
limit: number;
|
|
16
|
+
offset: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Builds the discriminator object for the forms list query key.
|
|
21
|
+
* Mirrors the params object used in createFormsQueries.list.
|
|
22
|
+
*/
|
|
23
|
+
export function formsListDiscriminator(params?: {
|
|
24
|
+
status?: "active" | "inactive" | "archived";
|
|
25
|
+
limit?: number;
|
|
26
|
+
offset?: number;
|
|
27
|
+
}): FormsListDiscriminator {
|
|
28
|
+
return {
|
|
29
|
+
status: params?.status,
|
|
30
|
+
limit: params?.limit ?? 20,
|
|
31
|
+
offset: params?.offset ?? 0,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Builds the discriminator object for the submissions list query key.
|
|
37
|
+
* Mirrors the params object used in createSubmissionsQueries.list.
|
|
38
|
+
*/
|
|
39
|
+
export function submissionsListDiscriminator(params: {
|
|
40
|
+
formId: string;
|
|
41
|
+
limit?: number;
|
|
42
|
+
offset?: number;
|
|
43
|
+
}): SubmissionsListDiscriminator {
|
|
44
|
+
return {
|
|
45
|
+
formId: params.formId,
|
|
46
|
+
limit: params.limit ?? 20,
|
|
47
|
+
offset: params.offset ?? 0,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Full query key builders — use these with queryClient.setQueryData() */
|
|
52
|
+
export const FORM_QUERY_KEYS = {
|
|
53
|
+
/**
|
|
54
|
+
* Key for forms.list(params) query.
|
|
55
|
+
* Full key: ["forms", "list", "list", { status, limit, offset }]
|
|
56
|
+
*/
|
|
57
|
+
formsList: (params?: {
|
|
58
|
+
status?: "active" | "inactive" | "archived";
|
|
59
|
+
limit?: number;
|
|
60
|
+
offset?: number;
|
|
61
|
+
}) => ["forms", "list", "list", formsListDiscriminator(params)] as const,
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Key for forms.byId(id) query.
|
|
65
|
+
* Full key: ["forms", "byId", "byId", id]
|
|
66
|
+
*/
|
|
67
|
+
formById: (id: string) => ["forms", "byId", "byId", id] as const,
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Key for formSubmissions.list(params) query.
|
|
71
|
+
* Full key: ["formSubmissions", "list", { formId, limit, offset }]
|
|
72
|
+
*/
|
|
73
|
+
submissionsList: (params: {
|
|
74
|
+
formId: string;
|
|
75
|
+
limit?: number;
|
|
76
|
+
offset?: number;
|
|
77
|
+
}) =>
|
|
78
|
+
["formSubmissions", "list", submissionsListDiscriminator(params)] as const,
|
|
79
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Re-exports serialization helpers from getters.ts for consumers who import
|
|
3
|
+
* from @btst/stack/plugins/form-builder/api.
|
|
4
|
+
*
|
|
5
|
+
* The actual implementations live in getters.ts alongside the DB functions
|
|
6
|
+
* they serialize so they stay in sync with the returned types.
|
|
7
|
+
*/
|
|
8
|
+
export {
|
|
9
|
+
serializeForm,
|
|
10
|
+
serializeFormSubmission,
|
|
11
|
+
serializeFormSubmissionWithData,
|
|
12
|
+
} from "./getters";
|
|
@@ -147,7 +147,7 @@ export function SubmissionsPage({ formId }: SubmissionsPageProps) {
|
|
|
147
147
|
{sub.id.slice(0, 8)}...
|
|
148
148
|
</TableCell>
|
|
149
149
|
<TableCell className="max-w-xs truncate text-sm text-muted-foreground">
|
|
150
|
-
{formatSubmissionData(sub.parsedData)}
|
|
150
|
+
{formatSubmissionData(sub.parsedData ?? {})}
|
|
151
151
|
</TableCell>
|
|
152
152
|
<TableCell className="text-muted-foreground">
|
|
153
153
|
{new Date(sub.submittedAt).toLocaleString()}
|
|
@@ -3,6 +3,7 @@ import { lazy } from "react";
|
|
|
3
3
|
import {
|
|
4
4
|
defineClientPlugin,
|
|
5
5
|
createApiClient,
|
|
6
|
+
isConnectionError,
|
|
6
7
|
} from "@btst/stack/plugins/client";
|
|
7
8
|
import { createRoute } from "@btst/yar";
|
|
8
9
|
import type { QueryClient } from "@tanstack/react-query";
|
|
@@ -197,6 +198,12 @@ function createFormListLoader(config: FormBuilderClientConfig) {
|
|
|
197
198
|
}
|
|
198
199
|
} catch (error) {
|
|
199
200
|
// Error hook - log the error but don't throw during SSR
|
|
201
|
+
if (isConnectionError(error)) {
|
|
202
|
+
console.warn(
|
|
203
|
+
"[btst/form-builder] route.loader() failed — no server running at build time. " +
|
|
204
|
+
"Use myStack.api.formBuilder.prefetchForRoute() for SSG data prefetching.",
|
|
205
|
+
);
|
|
206
|
+
}
|
|
200
207
|
if (hooks?.onLoadError) {
|
|
201
208
|
await hooks.onLoadError(error as Error, context);
|
|
202
209
|
}
|
|
@@ -265,6 +272,12 @@ function createFormBuilderLoader(
|
|
|
265
272
|
}
|
|
266
273
|
} catch (error) {
|
|
267
274
|
// Error hook - log the error but don't throw during SSR
|
|
275
|
+
if (isConnectionError(error)) {
|
|
276
|
+
console.warn(
|
|
277
|
+
"[btst/form-builder] route.loader() failed — no server running at build time. " +
|
|
278
|
+
"Use myStack.api.formBuilder.prefetchForRoute() for SSG data prefetching.",
|
|
279
|
+
);
|
|
280
|
+
}
|
|
268
281
|
if (hooks?.onLoadError) {
|
|
269
282
|
await hooks.onLoadError(error as Error, context);
|
|
270
283
|
}
|
|
@@ -364,6 +377,12 @@ function createSubmissionsLoader(
|
|
|
364
377
|
}
|
|
365
378
|
} catch (error) {
|
|
366
379
|
// Error hook - log the error but don't throw during SSR
|
|
380
|
+
if (isConnectionError(error)) {
|
|
381
|
+
console.warn(
|
|
382
|
+
"[btst/form-builder] route.loader() failed — no server running at build time. " +
|
|
383
|
+
"Use myStack.api.formBuilder.prefetchForRoute() for SSG data prefetching.",
|
|
384
|
+
);
|
|
385
|
+
}
|
|
367
386
|
if (hooks?.onLoadError) {
|
|
368
387
|
await hooks.onLoadError(error as Error, context);
|
|
369
388
|
}
|
|
@@ -10,6 +10,10 @@ import type {
|
|
|
10
10
|
PaginatedFormSubmissions,
|
|
11
11
|
SerializedFormSubmissionWithData,
|
|
12
12
|
} from "./types";
|
|
13
|
+
import {
|
|
14
|
+
formsListDiscriminator,
|
|
15
|
+
submissionsListDiscriminator,
|
|
16
|
+
} from "./api/query-key-defs";
|
|
13
17
|
|
|
14
18
|
interface FormListParams {
|
|
15
19
|
status?: "active" | "inactive" | "archived";
|
|
@@ -75,7 +79,7 @@ function createFormsQueries(
|
|
|
75
79
|
) {
|
|
76
80
|
return createQueryKeys("forms", {
|
|
77
81
|
list: (params: FormListParams = {}) => ({
|
|
78
|
-
queryKey: ["list", params],
|
|
82
|
+
queryKey: ["list", formsListDiscriminator(params)],
|
|
79
83
|
queryFn: async () => {
|
|
80
84
|
try {
|
|
81
85
|
const response: unknown = await client("/forms", {
|
|
@@ -147,7 +151,7 @@ function createSubmissionsQueries(
|
|
|
147
151
|
) {
|
|
148
152
|
return createQueryKeys("formSubmissions", {
|
|
149
153
|
list: (params: SubmissionListParams) => ({
|
|
150
|
-
queryKey: [params],
|
|
154
|
+
queryKey: [submissionsListDiscriminator(params)],
|
|
151
155
|
queryFn: async () => {
|
|
152
156
|
try {
|
|
153
157
|
const response: unknown = await client("/forms/:formId/submissions", {
|
|
@@ -82,8 +82,8 @@ export interface SerializedFormSubmission
|
|
|
82
82
|
export interface SerializedFormSubmissionWithData<
|
|
83
83
|
TData = Record<string, unknown>,
|
|
84
84
|
> extends SerializedFormSubmission {
|
|
85
|
-
/** Parsed data object (JSON.parse of data field) */
|
|
86
|
-
parsedData: TData;
|
|
85
|
+
/** Parsed data object (JSON.parse of data field). Null when the stored JSON is corrupted. */
|
|
86
|
+
parsedData: TData | null;
|
|
87
87
|
/** Joined form */
|
|
88
88
|
form?: SerializedForm;
|
|
89
89
|
}
|