@btst/stack 1.5.2 → 1.6.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 (201) hide show
  1. package/dist/node_modules/.pnpm/@dnd-kit_accessibility@3.1.1_react@19.2.0/node_modules/@dnd-kit/accessibility/dist/accessibility.esm.cjs +68 -0
  2. package/dist/node_modules/.pnpm/@dnd-kit_accessibility@3.1.1_react@19.2.0/node_modules/@dnd-kit/accessibility/dist/accessibility.esm.mjs +60 -0
  3. package/dist/node_modules/.pnpm/@dnd-kit_core@6.3.1_react-dom@19.2.0_react@19.2.0__react@19.2.0/node_modules/@dnd-kit/core/dist/core.esm.cjs +3937 -0
  4. package/dist/node_modules/.pnpm/@dnd-kit_core@6.3.1_react-dom@19.2.0_react@19.2.0__react@19.2.0/node_modules/@dnd-kit/core/dist/core.esm.mjs +3907 -0
  5. package/dist/node_modules/.pnpm/@dnd-kit_modifiers@9.0.0_@dnd-kit_core@6.3.1_react-dom@19.2.0_react@19.2.0__react@19.2.0__react@19.2.0/node_modules/@dnd-kit/modifiers/dist/modifiers.esm.cjs +30 -0
  6. package/dist/node_modules/.pnpm/@dnd-kit_modifiers@9.0.0_@dnd-kit_core@6.3.1_react-dom@19.2.0_react@19.2.0__react@19.2.0__react@19.2.0/node_modules/@dnd-kit/modifiers/dist/modifiers.esm.mjs +28 -0
  7. package/dist/node_modules/.pnpm/@dnd-kit_sortable@10.0.0_@dnd-kit_core@6.3.1_react-dom@19.2.0_react@19.2.0__react@19.2.0__react@19.2.0/node_modules/@dnd-kit/sortable/dist/sortable.esm.cjs +675 -0
  8. package/dist/node_modules/.pnpm/@dnd-kit_sortable@10.0.0_@dnd-kit_core@6.3.1_react-dom@19.2.0_react@19.2.0__react@19.2.0__react@19.2.0/node_modules/@dnd-kit/sortable/dist/sortable.esm.mjs +661 -0
  9. package/dist/node_modules/.pnpm/@dnd-kit_utilities@3.2.2_react@19.2.0/node_modules/@dnd-kit/utilities/dist/utilities.esm.cjs +358 -0
  10. package/dist/node_modules/.pnpm/@dnd-kit_utilities@3.2.2_react@19.2.0/node_modules/@dnd-kit/utilities/dist/utilities.esm.mjs +332 -0
  11. package/dist/node_modules/.pnpm/@radix-ui_react-tabs@1.1.13_@types_react-dom@19.2.3_@types_react@19.2.6__@types_react@1_865f042350eb43f3338b0fffb33f6246/node_modules/@radix-ui/react-tabs/dist/index.cjs +211 -0
  12. package/dist/node_modules/.pnpm/@radix-ui_react-tabs@1.1.13_@types_react-dom@19.2.3_@types_react@19.2.6__@types_react@1_865f042350eb43f3338b0fffb33f6246/node_modules/@radix-ui/react-tabs/dist/index.mjs +188 -0
  13. package/dist/packages/better-stack/src/plugins/cms/api/plugin.cjs +3 -2
  14. package/dist/packages/better-stack/src/plugins/cms/api/plugin.mjs +3 -2
  15. package/dist/packages/better-stack/src/plugins/cms/client/components/forms/content-form.cjs +15 -15
  16. package/dist/packages/better-stack/src/plugins/cms/client/components/forms/content-form.mjs +16 -16
  17. package/dist/packages/better-stack/src/plugins/form-builder/api/plugin.cjs +588 -0
  18. package/dist/packages/better-stack/src/plugins/form-builder/api/plugin.mjs +586 -0
  19. package/dist/packages/better-stack/src/plugins/form-builder/client/components/forms/form-renderer.cjs +131 -0
  20. package/dist/packages/better-stack/src/plugins/form-builder/client/components/forms/form-renderer.mjs +129 -0
  21. package/dist/packages/better-stack/src/plugins/form-builder/client/components/loading/form-builder-skeleton.cjs +32 -0
  22. package/dist/packages/better-stack/src/plugins/form-builder/client/components/loading/form-builder-skeleton.mjs +30 -0
  23. package/dist/packages/better-stack/src/plugins/form-builder/client/components/loading/form-list-skeleton.cjs +21 -0
  24. package/dist/packages/better-stack/src/plugins/form-builder/client/components/loading/form-list-skeleton.mjs +19 -0
  25. package/dist/packages/better-stack/src/plugins/form-builder/client/components/loading/submissions-skeleton.cjs +34 -0
  26. package/dist/packages/better-stack/src/plugins/form-builder/client/components/loading/submissions-skeleton.mjs +32 -0
  27. package/dist/packages/better-stack/src/plugins/form-builder/client/components/pages/404-page.cjs +20 -0
  28. package/dist/packages/better-stack/src/plugins/form-builder/client/components/pages/404-page.mjs +18 -0
  29. package/dist/packages/better-stack/src/plugins/form-builder/client/components/pages/form-builder-page.cjs +19 -0
  30. package/dist/packages/better-stack/src/plugins/form-builder/client/components/pages/form-builder-page.internal.cjs +186 -0
  31. package/dist/packages/better-stack/src/plugins/form-builder/client/components/pages/form-builder-page.internal.mjs +184 -0
  32. package/dist/packages/better-stack/src/plugins/form-builder/client/components/pages/form-builder-page.mjs +17 -0
  33. package/dist/packages/better-stack/src/plugins/form-builder/client/components/pages/form-list-page.cjs +19 -0
  34. package/dist/packages/better-stack/src/plugins/form-builder/client/components/pages/form-list-page.internal.cjs +165 -0
  35. package/dist/packages/better-stack/src/plugins/form-builder/client/components/pages/form-list-page.internal.mjs +163 -0
  36. package/dist/packages/better-stack/src/plugins/form-builder/client/components/pages/form-list-page.mjs +17 -0
  37. package/dist/packages/better-stack/src/plugins/form-builder/client/components/pages/submissions-page.cjs +19 -0
  38. package/dist/packages/better-stack/src/plugins/form-builder/client/components/pages/submissions-page.internal.cjs +177 -0
  39. package/dist/packages/better-stack/src/plugins/form-builder/client/components/pages/submissions-page.internal.mjs +175 -0
  40. package/dist/packages/better-stack/src/plugins/form-builder/client/components/pages/submissions-page.mjs +17 -0
  41. package/dist/packages/better-stack/src/plugins/form-builder/client/components/shared/default-error.cjs +17 -0
  42. package/dist/packages/better-stack/src/plugins/form-builder/client/components/shared/default-error.mjs +15 -0
  43. package/dist/packages/better-stack/src/plugins/form-builder/client/components/shared/empty-state.cjs +16 -0
  44. package/dist/packages/better-stack/src/plugins/form-builder/client/components/shared/empty-state.mjs +14 -0
  45. package/dist/packages/better-stack/src/plugins/form-builder/client/components/shared/page-wrapper.cjs +27 -0
  46. package/dist/packages/better-stack/src/plugins/form-builder/client/components/shared/page-wrapper.mjs +25 -0
  47. package/dist/packages/better-stack/src/plugins/form-builder/client/components/shared/pagination.cjs +39 -0
  48. package/dist/packages/better-stack/src/plugins/form-builder/client/components/shared/pagination.mjs +37 -0
  49. package/dist/packages/better-stack/src/plugins/form-builder/client/hooks/form-builder-hooks.cjs +551 -0
  50. package/dist/packages/better-stack/src/plugins/form-builder/client/hooks/form-builder-hooks.mjs +537 -0
  51. package/dist/packages/better-stack/src/plugins/form-builder/client/localization/form-builder-common.cjs +36 -0
  52. package/dist/packages/better-stack/src/plugins/form-builder/client/localization/form-builder-common.mjs +34 -0
  53. package/dist/packages/better-stack/src/plugins/form-builder/client/localization/form-builder-editor.cjs +19 -0
  54. package/dist/packages/better-stack/src/plugins/form-builder/client/localization/form-builder-editor.mjs +17 -0
  55. package/dist/packages/better-stack/src/plugins/form-builder/client/localization/form-builder-list.cjs +21 -0
  56. package/dist/packages/better-stack/src/plugins/form-builder/client/localization/form-builder-list.mjs +19 -0
  57. package/dist/packages/better-stack/src/plugins/form-builder/client/localization/form-builder-submissions.cjs +19 -0
  58. package/dist/packages/better-stack/src/plugins/form-builder/client/localization/form-builder-submissions.mjs +17 -0
  59. package/dist/packages/better-stack/src/plugins/form-builder/client/localization/form-builder-toasts.cjs +14 -0
  60. package/dist/packages/better-stack/src/plugins/form-builder/client/localization/form-builder-toasts.mjs +12 -0
  61. package/dist/packages/better-stack/src/plugins/form-builder/client/localization/index.cjs +17 -0
  62. package/dist/packages/better-stack/src/plugins/form-builder/client/localization/index.mjs +15 -0
  63. package/dist/packages/better-stack/src/plugins/form-builder/client/plugin.cjs +278 -0
  64. package/dist/packages/better-stack/src/plugins/form-builder/client/plugin.mjs +276 -0
  65. package/dist/packages/better-stack/src/plugins/form-builder/db.cjs +99 -0
  66. package/dist/packages/better-stack/src/plugins/form-builder/db.mjs +97 -0
  67. package/dist/packages/better-stack/src/plugins/form-builder/schemas.cjs +82 -0
  68. package/dist/packages/better-stack/src/plugins/form-builder/schemas.mjs +74 -0
  69. package/dist/packages/better-stack/src/plugins/form-builder/utils.cjs +37 -0
  70. package/dist/packages/better-stack/src/plugins/form-builder/utils.mjs +29 -0
  71. package/dist/packages/ui/src/components/auto-form/index.cjs +2 -12
  72. package/dist/packages/ui/src/components/auto-form/index.mjs +2 -9
  73. package/dist/packages/ui/src/components/auto-form/stepped-auto-form.cjs +377 -0
  74. package/dist/packages/ui/src/components/auto-form/stepped-auto-form.mjs +368 -0
  75. package/dist/packages/ui/src/components/auto-form/utils.cjs +1 -56
  76. package/dist/packages/ui/src/components/auto-form/utils.mjs +2 -56
  77. package/dist/packages/ui/src/components/form-builder/canvas.cjs +111 -0
  78. package/dist/packages/ui/src/components/form-builder/canvas.mjs +109 -0
  79. package/dist/packages/ui/src/components/form-builder/components/index.cjs +570 -0
  80. package/dist/packages/ui/src/components/form-builder/components/index.mjs +553 -0
  81. package/dist/packages/ui/src/components/form-builder/edit-field-dialog.cjs +131 -0
  82. package/dist/packages/ui/src/components/form-builder/edit-field-dialog.mjs +129 -0
  83. package/dist/packages/ui/src/components/form-builder/form-preview.cjs +73 -0
  84. package/dist/packages/ui/src/components/form-builder/form-preview.mjs +71 -0
  85. package/dist/packages/ui/src/components/form-builder/index.cjs +353 -0
  86. package/dist/packages/ui/src/components/form-builder/index.mjs +344 -0
  87. package/dist/packages/ui/src/components/form-builder/nested-field-editor-dialog.cjs +263 -0
  88. package/dist/packages/ui/src/components/form-builder/nested-field-editor-dialog.mjs +261 -0
  89. package/dist/packages/ui/src/components/form-builder/palette.cjs +52 -0
  90. package/dist/packages/ui/src/components/form-builder/palette.mjs +49 -0
  91. package/dist/packages/ui/src/components/form-builder/schema-utils.cjs +120 -0
  92. package/dist/packages/ui/src/components/form-builder/schema-utils.mjs +114 -0
  93. package/dist/packages/ui/src/components/form-builder/sortable-field.cjs +151 -0
  94. package/dist/packages/ui/src/components/form-builder/sortable-field.mjs +148 -0
  95. package/dist/packages/ui/src/components/form-builder/step-tabs.cjs +180 -0
  96. package/dist/packages/ui/src/components/form-builder/step-tabs.mjs +178 -0
  97. package/dist/packages/ui/src/components/form-builder/types.cjs +7 -0
  98. package/dist/packages/ui/src/components/form-builder/types.mjs +5 -0
  99. package/dist/packages/ui/src/components/form-builder/validation-schemas.cjs +67 -0
  100. package/dist/packages/ui/src/components/form-builder/validation-schemas.mjs +56 -0
  101. package/dist/packages/ui/src/components/tabs.cjs +70 -0
  102. package/dist/packages/ui/src/components/tabs.mjs +65 -0
  103. package/dist/packages/ui/src/lib/schema-converter.cjs +130 -0
  104. package/dist/packages/ui/src/lib/schema-converter.mjs +124 -0
  105. package/dist/plugins/blog/api/index.d.cts +1 -1
  106. package/dist/plugins/blog/api/index.d.mts +1 -1
  107. package/dist/plugins/blog/api/index.d.ts +1 -1
  108. package/dist/plugins/blog/client/hooks/index.d.cts +2 -2
  109. package/dist/plugins/blog/client/hooks/index.d.mts +2 -2
  110. package/dist/plugins/blog/client/hooks/index.d.ts +2 -2
  111. package/dist/plugins/blog/client/index.d.cts +1 -1
  112. package/dist/plugins/blog/client/index.d.mts +1 -1
  113. package/dist/plugins/blog/client/index.d.ts +1 -1
  114. package/dist/plugins/blog/query-keys.d.cts +2 -2
  115. package/dist/plugins/blog/query-keys.d.mts +2 -2
  116. package/dist/plugins/blog/query-keys.d.ts +2 -2
  117. package/dist/plugins/cms/client/index.cjs +6 -0
  118. package/dist/plugins/cms/client/index.d.cts +6 -113
  119. package/dist/plugins/cms/client/index.d.mts +6 -113
  120. package/dist/plugins/cms/client/index.d.ts +6 -113
  121. package/dist/plugins/cms/client/index.mjs +1 -0
  122. package/dist/plugins/form-builder/api/index.cjs +7 -0
  123. package/dist/plugins/form-builder/api/index.d.cts +141 -0
  124. package/dist/plugins/form-builder/api/index.d.mts +141 -0
  125. package/dist/plugins/form-builder/api/index.d.ts +141 -0
  126. package/dist/plugins/form-builder/api/index.mjs +1 -0
  127. package/dist/plugins/form-builder/client/components/index.cjs +29 -0
  128. package/dist/plugins/form-builder/client/components/index.d.cts +93 -0
  129. package/dist/plugins/form-builder/client/components/index.d.mts +93 -0
  130. package/dist/plugins/form-builder/client/components/index.d.ts +93 -0
  131. package/dist/plugins/form-builder/client/components/index.mjs +18 -0
  132. package/dist/plugins/form-builder/client/hooks/index.cjs +19 -0
  133. package/dist/plugins/form-builder/client/hooks/index.d.cts +154 -0
  134. package/dist/plugins/form-builder/client/hooks/index.d.mts +154 -0
  135. package/dist/plugins/form-builder/client/hooks/index.d.ts +154 -0
  136. package/dist/plugins/form-builder/client/hooks/index.mjs +1 -0
  137. package/dist/plugins/form-builder/client/index.cjs +13 -0
  138. package/dist/plugins/form-builder/client/index.d.cts +381 -0
  139. package/dist/plugins/form-builder/client/index.d.mts +381 -0
  140. package/dist/plugins/form-builder/client/index.d.ts +381 -0
  141. package/dist/plugins/form-builder/client/index.mjs +2 -0
  142. package/dist/plugins/form-builder/client.css +3 -0
  143. package/dist/plugins/form-builder/query-keys.cjs +143 -0
  144. package/dist/plugins/form-builder/query-keys.d.cts +74 -0
  145. package/dist/plugins/form-builder/query-keys.d.mts +74 -0
  146. package/dist/plugins/form-builder/query-keys.d.ts +74 -0
  147. package/dist/plugins/form-builder/query-keys.mjs +141 -0
  148. package/dist/plugins/form-builder/style.css +19 -0
  149. package/dist/shared/stack.AX5nZ6A3.d.cts +86 -0
  150. package/dist/shared/stack.AX5nZ6A3.d.mts +86 -0
  151. package/dist/shared/stack.AX5nZ6A3.d.ts +86 -0
  152. package/dist/shared/stack.BIh2AXaW.d.cts +123 -0
  153. package/dist/shared/stack.BIh2AXaW.d.mts +123 -0
  154. package/dist/shared/stack.BIh2AXaW.d.ts +123 -0
  155. package/dist/shared/stack.DzH_wcvr.d.cts +195 -0
  156. package/dist/shared/stack.DzH_wcvr.d.mts +195 -0
  157. package/dist/shared/stack.DzH_wcvr.d.ts +195 -0
  158. package/package.json +54 -1
  159. package/src/plugins/cms/api/plugin.ts +9 -4
  160. package/src/plugins/cms/client/components/forms/content-form.tsx +23 -25
  161. package/src/plugins/cms/client/index.ts +11 -0
  162. package/src/plugins/form-builder/api/index.ts +1 -0
  163. package/src/plugins/form-builder/api/plugin.ts +776 -0
  164. package/src/plugins/form-builder/client/components/forms/form-renderer.tsx +253 -0
  165. package/src/plugins/form-builder/client/components/index.tsx +24 -0
  166. package/src/plugins/form-builder/client/components/loading/form-builder-skeleton.tsx +42 -0
  167. package/src/plugins/form-builder/client/components/loading/form-list-skeleton.tsx +25 -0
  168. package/src/plugins/form-builder/client/components/loading/index.tsx +3 -0
  169. package/src/plugins/form-builder/client/components/loading/submissions-skeleton.tsx +40 -0
  170. package/src/plugins/form-builder/client/components/pages/404-page.tsx +28 -0
  171. package/src/plugins/form-builder/client/components/pages/form-builder-page.internal.tsx +253 -0
  172. package/src/plugins/form-builder/client/components/pages/form-builder-page.tsx +26 -0
  173. package/src/plugins/form-builder/client/components/pages/form-list-page.internal.tsx +231 -0
  174. package/src/plugins/form-builder/client/components/pages/form-list-page.tsx +22 -0
  175. package/src/plugins/form-builder/client/components/pages/submissions-page.internal.tsx +268 -0
  176. package/src/plugins/form-builder/client/components/pages/submissions-page.tsx +26 -0
  177. package/src/plugins/form-builder/client/components/shared/default-error.tsx +30 -0
  178. package/src/plugins/form-builder/client/components/shared/empty-state.tsx +26 -0
  179. package/src/plugins/form-builder/client/components/shared/page-wrapper.tsx +32 -0
  180. package/src/plugins/form-builder/client/components/shared/pagination.tsx +52 -0
  181. package/src/plugins/form-builder/client/hooks/form-builder-hooks.tsx +799 -0
  182. package/src/plugins/form-builder/client/hooks/index.tsx +1 -0
  183. package/src/plugins/form-builder/client/index.ts +22 -0
  184. package/src/plugins/form-builder/client/localization/form-builder-common.ts +36 -0
  185. package/src/plugins/form-builder/client/localization/form-builder-editor.ts +18 -0
  186. package/src/plugins/form-builder/client/localization/form-builder-list.ts +17 -0
  187. package/src/plugins/form-builder/client/localization/form-builder-submissions.ts +17 -0
  188. package/src/plugins/form-builder/client/localization/form-builder-toasts.ts +10 -0
  189. package/src/plugins/form-builder/client/localization/index.ts +15 -0
  190. package/src/plugins/form-builder/client/overrides.ts +146 -0
  191. package/src/plugins/form-builder/client/plugin.tsx +488 -0
  192. package/src/plugins/form-builder/client.css +3 -0
  193. package/src/plugins/form-builder/db.ts +99 -0
  194. package/src/plugins/form-builder/query-keys.ts +198 -0
  195. package/src/plugins/form-builder/schemas.ts +122 -0
  196. package/src/plugins/form-builder/style.css +19 -0
  197. package/src/plugins/form-builder/types.ts +317 -0
  198. package/src/plugins/form-builder/utils.ts +63 -0
  199. package/dist/shared/{stack.DLhzx1-D.d.cts → stack.CcI4sYJP.d.cts} +1 -1
  200. package/dist/shared/{stack.DLhzx1-D.d.mts → stack.CcI4sYJP.d.mts} +1 -1
  201. package/dist/shared/{stack.DLhzx1-D.d.ts → stack.CcI4sYJP.d.ts} +1 -1
@@ -0,0 +1,586 @@
1
+ import { defineBackendPlugin, createEndpoint } from '@btst/stack/plugins/api';
2
+ import { z } from 'zod';
3
+ import { formSchemaToZod } from '../../../../../ui/src/lib/schema-converter.mjs';
4
+ import { formBuilderSchema } from '../db.mjs';
5
+ import { listFormsQuerySchema, createFormSchema, updateFormSchema, listSubmissionsQuerySchema } from '../schemas.mjs';
6
+ import { extractUserAgent, extractIpAddress, slugify } from '../utils.mjs';
7
+
8
+ function serializeForm(form) {
9
+ return {
10
+ id: form.id,
11
+ name: form.name,
12
+ slug: form.slug,
13
+ description: form.description,
14
+ schema: form.schema,
15
+ successMessage: form.successMessage,
16
+ redirectUrl: form.redirectUrl,
17
+ status: form.status,
18
+ createdBy: form.createdBy,
19
+ createdAt: form.createdAt.toISOString(),
20
+ updatedAt: form.updatedAt.toISOString()
21
+ };
22
+ }
23
+ function serializeFormSubmission(submission) {
24
+ return {
25
+ ...submission,
26
+ submittedAt: submission.submittedAt.toISOString()
27
+ };
28
+ }
29
+ function serializeFormSubmissionWithData(submission) {
30
+ return {
31
+ ...serializeFormSubmission(submission),
32
+ parsedData: JSON.parse(submission.data),
33
+ form: submission.form ? serializeForm(submission.form) : void 0
34
+ };
35
+ }
36
+ const formBuilderBackendPlugin = (config = {}) => defineBackendPlugin({
37
+ name: "form-builder",
38
+ dbPlugin: formBuilderSchema,
39
+ routes: (adapter) => {
40
+ const createContext = (headers) => ({
41
+ headers,
42
+ ipAddress: extractIpAddress(headers),
43
+ userAgent: extractUserAgent(headers)
44
+ });
45
+ const createSubmissionContext = (formSlug, formId, headers) => ({
46
+ ...createContext(headers),
47
+ formSlug,
48
+ formId
49
+ });
50
+ const listForms = createEndpoint(
51
+ "/forms",
52
+ {
53
+ method: "GET",
54
+ query: listFormsQuerySchema
55
+ },
56
+ async (ctx) => {
57
+ const { status, limit, offset } = ctx.query;
58
+ const context = createContext(ctx.headers);
59
+ if (config.hooks?.onBeforeListForms) {
60
+ const canList = await config.hooks.onBeforeListForms(context);
61
+ if (!canList) {
62
+ throw ctx.error(403, { message: "Access denied" });
63
+ }
64
+ }
65
+ const whereConditions = [];
66
+ if (status) {
67
+ whereConditions.push({
68
+ field: "status",
69
+ value: status,
70
+ operator: "eq"
71
+ });
72
+ }
73
+ const allForms = await adapter.findMany({
74
+ model: "form",
75
+ where: whereConditions.length > 0 ? whereConditions : void 0
76
+ });
77
+ const total = allForms.length;
78
+ const forms = await adapter.findMany({
79
+ model: "form",
80
+ where: whereConditions.length > 0 ? whereConditions : void 0,
81
+ limit,
82
+ offset,
83
+ sortBy: { field: "createdAt", direction: "desc" }
84
+ });
85
+ return {
86
+ items: forms.map(serializeForm),
87
+ total,
88
+ limit,
89
+ offset
90
+ };
91
+ }
92
+ );
93
+ const getFormBySlug = createEndpoint(
94
+ "/forms/:slug",
95
+ {
96
+ method: "GET",
97
+ params: z.object({ slug: z.string() })
98
+ },
99
+ async (ctx) => {
100
+ const { slug } = ctx.params;
101
+ const context = createContext(ctx.headers);
102
+ if (config.hooks?.onBeforeGetForm) {
103
+ const canGet = await config.hooks.onBeforeGetForm(slug, context);
104
+ if (!canGet) {
105
+ throw ctx.error(403, { message: "Access denied" });
106
+ }
107
+ }
108
+ const form = await adapter.findOne({
109
+ model: "form",
110
+ where: [{ field: "slug", value: slug, operator: "eq" }]
111
+ });
112
+ if (!form) {
113
+ throw ctx.error(404, { message: "Form not found" });
114
+ }
115
+ return serializeForm(form);
116
+ }
117
+ );
118
+ const getFormById = createEndpoint(
119
+ "/forms/id/:id",
120
+ {
121
+ method: "GET",
122
+ params: z.object({ id: z.string() })
123
+ },
124
+ async (ctx) => {
125
+ const { id } = ctx.params;
126
+ const context = createContext(ctx.headers);
127
+ if (config.hooks?.onBeforeGetForm) {
128
+ const canGet = await config.hooks.onBeforeGetForm(id, context);
129
+ if (!canGet) {
130
+ throw ctx.error(403, { message: "Access denied" });
131
+ }
132
+ }
133
+ const form = await adapter.findOne({
134
+ model: "form",
135
+ where: [{ field: "id", value: id, operator: "eq" }]
136
+ });
137
+ if (!form) {
138
+ throw ctx.error(404, { message: "Form not found" });
139
+ }
140
+ return serializeForm(form);
141
+ }
142
+ );
143
+ const createForm = createEndpoint(
144
+ "/forms",
145
+ {
146
+ method: "POST",
147
+ body: createFormSchema
148
+ },
149
+ async (ctx) => {
150
+ const body = ctx.body;
151
+ const context = createContext(ctx.headers);
152
+ const slug = slugify(body.slug);
153
+ if (!slug) {
154
+ throw ctx.error(400, {
155
+ message: "Invalid slug: must contain at least one alphanumeric character"
156
+ });
157
+ }
158
+ const existing = await adapter.findOne({
159
+ model: "form",
160
+ where: [{ field: "slug", value: slug, operator: "eq" }]
161
+ });
162
+ if (existing) {
163
+ throw ctx.error(409, {
164
+ message: "Form with this slug already exists"
165
+ });
166
+ }
167
+ try {
168
+ JSON.parse(body.schema);
169
+ } catch {
170
+ throw ctx.error(400, { message: "Invalid JSON Schema" });
171
+ }
172
+ let formInput = {
173
+ name: body.name,
174
+ slug,
175
+ description: body.description,
176
+ schema: body.schema,
177
+ successMessage: body.successMessage,
178
+ redirectUrl: body.redirectUrl || void 0,
179
+ status: body.status
180
+ };
181
+ if (config.hooks?.onBeforeFormCreated) {
182
+ const result = await config.hooks.onBeforeFormCreated(
183
+ formInput,
184
+ context
185
+ );
186
+ if (result === false) {
187
+ throw ctx.error(403, { message: "Create operation denied" });
188
+ }
189
+ if (result && typeof result === "object") {
190
+ formInput = result;
191
+ }
192
+ }
193
+ const form = await adapter.create({
194
+ model: "form",
195
+ data: {
196
+ name: formInput.name,
197
+ slug: formInput.slug,
198
+ description: formInput.description,
199
+ schema: formInput.schema,
200
+ successMessage: formInput.successMessage,
201
+ redirectUrl: formInput.redirectUrl,
202
+ status: formInput.status || "active",
203
+ createdBy: formInput.createdBy,
204
+ createdAt: /* @__PURE__ */ new Date(),
205
+ updatedAt: /* @__PURE__ */ new Date()
206
+ }
207
+ });
208
+ const serialized = serializeForm(form);
209
+ if (config.hooks?.onAfterFormCreated) {
210
+ await config.hooks.onAfterFormCreated(serialized, context);
211
+ }
212
+ return serialized;
213
+ }
214
+ );
215
+ const updateForm = createEndpoint(
216
+ "/forms/:id",
217
+ {
218
+ method: "PUT",
219
+ params: z.object({ id: z.string() }),
220
+ body: updateFormSchema
221
+ },
222
+ async (ctx) => {
223
+ const { id } = ctx.params;
224
+ const body = ctx.body;
225
+ const context = createContext(ctx.headers);
226
+ const existing = await adapter.findOne({
227
+ model: "form",
228
+ where: [{ field: "id", value: id, operator: "eq" }]
229
+ });
230
+ if (!existing) {
231
+ throw ctx.error(404, { message: "Form not found" });
232
+ }
233
+ let slug;
234
+ if (body.slug) {
235
+ slug = slugify(body.slug);
236
+ if (!slug) {
237
+ throw ctx.error(400, {
238
+ message: "Invalid slug: must contain at least one alphanumeric character"
239
+ });
240
+ }
241
+ if (slug !== existing.slug) {
242
+ const duplicate = await adapter.findOne({
243
+ model: "form",
244
+ where: [
245
+ { field: "slug", value: slug, operator: "eq" }
246
+ ]
247
+ });
248
+ if (duplicate) {
249
+ throw ctx.error(409, {
250
+ message: "Form with this slug already exists"
251
+ });
252
+ }
253
+ }
254
+ }
255
+ if (body.schema) {
256
+ try {
257
+ JSON.parse(body.schema);
258
+ } catch {
259
+ throw ctx.error(400, { message: "Invalid JSON Schema" });
260
+ }
261
+ }
262
+ let updateInput = {
263
+ name: body.name,
264
+ slug,
265
+ description: body.description,
266
+ schema: body.schema,
267
+ successMessage: body.successMessage,
268
+ redirectUrl: body.redirectUrl,
269
+ status: body.status
270
+ };
271
+ if (config.hooks?.onBeforeFormUpdated) {
272
+ const result = await config.hooks.onBeforeFormUpdated(
273
+ id,
274
+ updateInput,
275
+ context
276
+ );
277
+ if (result === false) {
278
+ throw ctx.error(403, { message: "Update operation denied" });
279
+ }
280
+ if (result && typeof result === "object") {
281
+ updateInput = result;
282
+ }
283
+ }
284
+ const updateData = {
285
+ updatedAt: /* @__PURE__ */ new Date()
286
+ };
287
+ if (updateInput.name) updateData.name = updateInput.name;
288
+ if (updateInput.slug) updateData.slug = updateInput.slug;
289
+ if (updateInput.description !== void 0)
290
+ updateData.description = updateInput.description;
291
+ if (updateInput.schema) updateData.schema = updateInput.schema;
292
+ if (updateInput.successMessage !== void 0)
293
+ updateData.successMessage = updateInput.successMessage;
294
+ if (updateInput.redirectUrl !== void 0)
295
+ updateData.redirectUrl = updateInput.redirectUrl;
296
+ if (updateInput.status) updateData.status = updateInput.status;
297
+ await adapter.update({
298
+ model: "form",
299
+ where: [{ field: "id", value: id, operator: "eq" }],
300
+ update: updateData
301
+ });
302
+ const updated = await adapter.findOne({
303
+ model: "form",
304
+ where: [{ field: "id", value: id, operator: "eq" }]
305
+ });
306
+ if (!updated) {
307
+ throw ctx.error(500, { message: "Failed to fetch updated form" });
308
+ }
309
+ const serialized = serializeForm(updated);
310
+ if (config.hooks?.onAfterFormUpdated) {
311
+ await config.hooks.onAfterFormUpdated(serialized, context);
312
+ }
313
+ return serialized;
314
+ }
315
+ );
316
+ const deleteForm = createEndpoint(
317
+ "/forms/:id",
318
+ {
319
+ method: "DELETE",
320
+ params: z.object({ id: z.string() })
321
+ },
322
+ async (ctx) => {
323
+ const { id } = ctx.params;
324
+ const context = createContext(ctx.headers);
325
+ const existing = await adapter.findOne({
326
+ model: "form",
327
+ where: [{ field: "id", value: id, operator: "eq" }]
328
+ });
329
+ if (!existing) {
330
+ throw ctx.error(404, { message: "Form not found" });
331
+ }
332
+ if (config.hooks?.onBeforeFormDeleted) {
333
+ const canDelete = await config.hooks.onBeforeFormDeleted(
334
+ id,
335
+ context
336
+ );
337
+ if (!canDelete) {
338
+ throw ctx.error(403, { message: "Delete operation denied" });
339
+ }
340
+ }
341
+ await adapter.delete({
342
+ model: "formSubmission",
343
+ where: [{ field: "formId", value: id, operator: "eq" }]
344
+ });
345
+ await adapter.delete({
346
+ model: "form",
347
+ where: [{ field: "id", value: id, operator: "eq" }]
348
+ });
349
+ if (config.hooks?.onAfterFormDeleted) {
350
+ await config.hooks.onAfterFormDeleted(id, context);
351
+ }
352
+ return { success: true };
353
+ }
354
+ );
355
+ const submitForm = createEndpoint(
356
+ "/forms/:slug/submit",
357
+ {
358
+ method: "POST",
359
+ params: z.object({ slug: z.string() }),
360
+ body: z.object({
361
+ // Use passthrough object for dynamic form data
362
+ data: z.object({}).passthrough()
363
+ })
364
+ },
365
+ async (ctx) => {
366
+ const { slug } = ctx.params;
367
+ const { data } = ctx.body;
368
+ const baseContext = createContext(ctx.headers);
369
+ const form = await adapter.findOne({
370
+ model: "form",
371
+ where: [{ field: "slug", value: slug, operator: "eq" }]
372
+ });
373
+ if (!form) {
374
+ throw ctx.error(404, { message: "Form not found" });
375
+ }
376
+ if (form.status !== "active") {
377
+ throw ctx.error(400, {
378
+ message: "Form is not accepting submissions"
379
+ });
380
+ }
381
+ const submissionContext = createSubmissionContext(
382
+ slug,
383
+ form.id,
384
+ ctx.headers
385
+ );
386
+ try {
387
+ const jsonSchema = JSON.parse(form.schema);
388
+ const zodSchema = formSchemaToZod(jsonSchema);
389
+ const validation = zodSchema.safeParse(data);
390
+ if (!validation.success) {
391
+ throw ctx.error(400, {
392
+ message: "Validation failed",
393
+ errors: validation.error.issues
394
+ });
395
+ }
396
+ } catch (error) {
397
+ if (error && typeof error === "object" && "code" in error) {
398
+ throw error;
399
+ }
400
+ throw ctx.error(400, { message: "Invalid form data" });
401
+ }
402
+ let finalData = data;
403
+ if (config.hooks?.onBeforeSubmission) {
404
+ try {
405
+ const result = await config.hooks.onBeforeSubmission(
406
+ slug,
407
+ data,
408
+ submissionContext
409
+ );
410
+ if (result === false) {
411
+ throw ctx.error(400, { message: "Submission rejected" });
412
+ }
413
+ if (result && typeof result === "object") {
414
+ finalData = result;
415
+ }
416
+ } catch (error) {
417
+ if (config.hooks?.onSubmissionError) {
418
+ await config.hooks.onSubmissionError(
419
+ error,
420
+ slug,
421
+ data,
422
+ submissionContext
423
+ );
424
+ }
425
+ throw error;
426
+ }
427
+ }
428
+ const submission = await adapter.create({
429
+ model: "formSubmission",
430
+ data: {
431
+ formId: form.id,
432
+ data: JSON.stringify(finalData),
433
+ submittedAt: /* @__PURE__ */ new Date(),
434
+ ipAddress: baseContext.ipAddress,
435
+ userAgent: baseContext.userAgent
436
+ }
437
+ });
438
+ const serialized = serializeFormSubmission(submission);
439
+ if (config.hooks?.onAfterSubmission) {
440
+ await config.hooks.onAfterSubmission(
441
+ serialized,
442
+ serializeForm(form),
443
+ submissionContext
444
+ );
445
+ }
446
+ return {
447
+ ...serialized,
448
+ form: {
449
+ successMessage: form.successMessage,
450
+ redirectUrl: form.redirectUrl
451
+ }
452
+ };
453
+ }
454
+ );
455
+ const listSubmissions = createEndpoint(
456
+ "/forms/:formId/submissions",
457
+ {
458
+ method: "GET",
459
+ params: z.object({ formId: z.string() }),
460
+ query: listSubmissionsQuerySchema
461
+ },
462
+ async (ctx) => {
463
+ const { formId } = ctx.params;
464
+ const { limit, offset } = ctx.query;
465
+ const context = createContext(ctx.headers);
466
+ const form = await adapter.findOne({
467
+ model: "form",
468
+ where: [{ field: "id", value: formId, operator: "eq" }]
469
+ });
470
+ if (!form) {
471
+ throw ctx.error(404, { message: "Form not found" });
472
+ }
473
+ if (config.hooks?.onBeforeListSubmissions) {
474
+ const canList = await config.hooks.onBeforeListSubmissions(
475
+ formId,
476
+ context
477
+ );
478
+ if (!canList) {
479
+ throw ctx.error(403, { message: "Access denied" });
480
+ }
481
+ }
482
+ const allSubmissions = await adapter.findMany({
483
+ model: "formSubmission",
484
+ where: [
485
+ { field: "formId", value: formId, operator: "eq" }
486
+ ]
487
+ });
488
+ const total = allSubmissions.length;
489
+ const submissions = await adapter.findMany({
490
+ model: "formSubmission",
491
+ where: [
492
+ { field: "formId", value: formId, operator: "eq" }
493
+ ],
494
+ limit,
495
+ offset,
496
+ sortBy: { field: "submittedAt", direction: "desc" },
497
+ join: { form: true }
498
+ });
499
+ return {
500
+ items: submissions.map(serializeFormSubmissionWithData),
501
+ total,
502
+ limit,
503
+ offset
504
+ };
505
+ }
506
+ );
507
+ const getSubmission = createEndpoint(
508
+ "/forms/:formId/submissions/:subId",
509
+ {
510
+ method: "GET",
511
+ params: z.object({ formId: z.string(), subId: z.string() })
512
+ },
513
+ async (ctx) => {
514
+ const { formId, subId } = ctx.params;
515
+ const context = createContext(ctx.headers);
516
+ if (config.hooks?.onBeforeGetSubmission) {
517
+ const canGet = await config.hooks.onBeforeGetSubmission(
518
+ subId,
519
+ context
520
+ );
521
+ if (!canGet) {
522
+ throw ctx.error(403, { message: "Access denied" });
523
+ }
524
+ }
525
+ const submission = await adapter.findOne({
526
+ model: "formSubmission",
527
+ where: [{ field: "id", value: subId, operator: "eq" }],
528
+ join: { form: true }
529
+ });
530
+ if (!submission || submission.formId !== formId) {
531
+ throw ctx.error(404, { message: "Submission not found" });
532
+ }
533
+ return serializeFormSubmissionWithData(submission);
534
+ }
535
+ );
536
+ const deleteSubmission = createEndpoint(
537
+ "/forms/:formId/submissions/:subId",
538
+ {
539
+ method: "DELETE",
540
+ params: z.object({ formId: z.string(), subId: z.string() })
541
+ },
542
+ async (ctx) => {
543
+ const { formId, subId } = ctx.params;
544
+ const context = createContext(ctx.headers);
545
+ const existing = await adapter.findOne({
546
+ model: "formSubmission",
547
+ where: [{ field: "id", value: subId, operator: "eq" }]
548
+ });
549
+ if (!existing || existing.formId !== formId) {
550
+ throw ctx.error(404, { message: "Submission not found" });
551
+ }
552
+ if (config.hooks?.onBeforeSubmissionDeleted) {
553
+ const canDelete = await config.hooks.onBeforeSubmissionDeleted(
554
+ subId,
555
+ context
556
+ );
557
+ if (!canDelete) {
558
+ throw ctx.error(403, { message: "Delete operation denied" });
559
+ }
560
+ }
561
+ await adapter.delete({
562
+ model: "formSubmission",
563
+ where: [{ field: "id", value: subId, operator: "eq" }]
564
+ });
565
+ if (config.hooks?.onAfterSubmissionDeleted) {
566
+ await config.hooks.onAfterSubmissionDeleted(subId, context);
567
+ }
568
+ return { success: true };
569
+ }
570
+ );
571
+ return {
572
+ listForms,
573
+ getFormBySlug,
574
+ getFormById,
575
+ createForm,
576
+ updateForm,
577
+ deleteForm,
578
+ submitForm,
579
+ listSubmissions,
580
+ getSubmission,
581
+ deleteSubmission
582
+ };
583
+ }
584
+ });
585
+
586
+ export { formBuilderBackendPlugin };