@nextlyhq/plugin-form-builder 0.0.1

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 (37) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +122 -0
  3. package/dist/CheckboxFieldEditor-K4T6QX52.js +121 -0
  4. package/dist/CheckboxFieldEditor-K4T6QX52.js.map +1 -0
  5. package/dist/DateFieldEditor-JFJFAVOH.js +187 -0
  6. package/dist/DateFieldEditor-JFJFAVOH.js.map +1 -0
  7. package/dist/EmailFieldEditor-MMOXXL7P.js +138 -0
  8. package/dist/EmailFieldEditor-MMOXXL7P.js.map +1 -0
  9. package/dist/NumberFieldEditor-UISAYR6Z.js +175 -0
  10. package/dist/NumberFieldEditor-UISAYR6Z.js.map +1 -0
  11. package/dist/RadioFieldEditor-6UQFH4WT.js +165 -0
  12. package/dist/RadioFieldEditor-6UQFH4WT.js.map +1 -0
  13. package/dist/SelectFieldEditor-PSO4KHV4.js +213 -0
  14. package/dist/SelectFieldEditor-PSO4KHV4.js.map +1 -0
  15. package/dist/TextFieldEditor-5EU64EA5.js +187 -0
  16. package/dist/TextFieldEditor-5EU64EA5.js.map +1 -0
  17. package/dist/TextareaFieldEditor-773G3LNM.js +190 -0
  18. package/dist/TextareaFieldEditor-773G3LNM.js.map +1 -0
  19. package/dist/admin/index.css +934 -0
  20. package/dist/admin/index.css.map +1 -0
  21. package/dist/admin/index.d.ts +547 -0
  22. package/dist/admin/index.js +4030 -0
  23. package/dist/admin/index.js.map +1 -0
  24. package/dist/chunk-ABNZFTQK.js +586 -0
  25. package/dist/chunk-ABNZFTQK.js.map +1 -0
  26. package/dist/chunk-XTCEPJ7B.js +256 -0
  27. package/dist/chunk-XTCEPJ7B.js.map +1 -0
  28. package/dist/components/index.d.ts +3 -0
  29. package/dist/components/index.js +9 -0
  30. package/dist/components/index.js.map +1 -0
  31. package/dist/index-C0kBvYjm.d.ts +1012 -0
  32. package/dist/index.d.ts +1663 -0
  33. package/dist/index.js +2470 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/styles/form-builder.css +1150 -0
  36. package/dist/styles/submissions-filter.css +75 -0
  37. package/package.json +96 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/plugin.ts","../src/collections/forms.ts","../src/collections/submissions.ts","../src/types.ts","../src/config/validate-form.ts","../src/config/defaults.ts","../src/config/field-helpers.ts","../src/utils/evaluate-conditions.ts","../src/utils/generate-schema.ts","../src/utils/export-formats.ts","../src/handlers/spam-detection.ts","../src/handlers/submit-form.ts","../src/handlers/webhooks.ts","../src/index.ts"],"sourcesContent":["/**\n * Form Builder Plugin\n *\n * Visual form builder with drag-and-drop UI, submission management,\n * email notifications, and spam protection.\n *\n * @module plugin\n * @since 0.1.0\n */\n\nimport type { CollectionConfig, PluginDefinition } from \"nextly\";\n// `getCollectionsHandler` runs inside Next.js request handlers, so it\n// lives behind the runtime subpath. Importing from\n// the root would drag Next.js subpaths into Node-only contexts (CLI,\n// config loaders) that pull this plugin via `nextly.config.ts`.\nimport { getCollectionsHandler } from \"nextly/runtime\";\n\nimport { formsCollection } from \"./collections/forms\";\nimport { submissionsCollection } from \"./collections/submissions\";\nimport type {\n FormNotificationItem,\n FormBuilderPluginOptions,\n ResolvedFormBuilderConfig,\n} from \"./types\";\n\nexport type NextlyPlugin = PluginDefinition;\n\n/** The runtime instance passed to plugin hooks (type extracted from `init`'s parameter). */\ntype NextlyInstance = Parameters<NonNullable<NextlyPlugin[\"init\"]>>[0];\n\n/** Internal augmentation: we stash the resolved config on the nextly instance for later retrieval. */\ntype NextlyWithFormBuilderConfig = NextlyInstance & {\n __formBuilderConfig?: ResolvedFormBuilderConfig;\n};\n\n// ---------------------------------------------------------------------------\n// Configuration resolver\n// ---------------------------------------------------------------------------\n\n/**\n * Merge user options with sensible defaults.\n */\nfunction resolveConfig(\n options: FormBuilderPluginOptions\n): ResolvedFormBuilderConfig {\n const formOverrides =\n options.formOverrides || options.collections?.forms || {};\n const submissionOverrides =\n options.formSubmissionOverrides || options.collections?.submissions || {};\n\n return {\n formOverrides: {\n ...formOverrides,\n slug: formOverrides.slug || \"forms\",\n labels: {\n singular: formOverrides.labels?.singular || \"Form\",\n plural: formOverrides.labels?.plural || \"Forms\",\n },\n },\n\n formSubmissionOverrides: {\n ...submissionOverrides,\n slug: submissionOverrides.slug || \"form-submissions\",\n labels: {\n singular: submissionOverrides.labels?.singular || \"Submission\",\n plural: submissionOverrides.labels?.plural || \"Submissions\",\n },\n },\n\n fields: {\n text: options.fields?.text ?? true,\n email: options.fields?.email ?? true,\n number: options.fields?.number ?? true,\n phone: options.fields?.phone ?? true,\n url: options.fields?.url ?? true,\n textarea: options.fields?.textarea ?? true,\n select: options.fields?.select ?? true,\n checkbox: options.fields?.checkbox ?? true,\n radio: options.fields?.radio ?? true,\n file: options.fields?.file ?? true,\n date: options.fields?.date ?? true,\n time: options.fields?.time ?? true,\n hidden: options.fields?.hidden ?? true,\n },\n\n redirectRelationships: options.redirectRelationships || [],\n beforeEmail: options.beforeEmail,\n\n notifications: {\n defaultFrom: options.notifications?.defaultFrom,\n defaultToEmail: options.notifications?.defaultToEmail,\n enabled: options.notifications?.enabled ?? true,\n },\n\n spamProtection: {\n honeypot: options.spamProtection?.honeypot ?? true,\n recaptcha: options.spamProtection?.recaptcha ?? { enabled: false },\n rateLimit: {\n maxSubmissions: options.spamProtection?.rateLimit?.maxSubmissions ?? 10,\n windowMs: options.spamProtection?.rateLimit?.windowMs ?? 60_000,\n },\n },\n\n uploads: {\n maxFileSize: options.uploads?.maxFileSize ?? 10_485_760, // 10 MB\n allowedMimeTypes: options.uploads?.allowedMimeTypes ?? [\n \"image/*\",\n \"application/pdf\",\n \"text/*\",\n ],\n uploadCollection: options.uploads?.uploadCollection ?? \"media\",\n },\n\n features: {\n builder: options.features?.builder ?? true,\n conditionalLogic: options.features?.conditionalLogic ?? true,\n fileUploads: options.features?.fileUploads ?? true,\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\nexport interface FormBuilderPluginResult {\n /** Plugin definition — pass to `defineConfig({ plugins: [...] })`. */\n plugin: NextlyPlugin;\n /** Forms & Submissions collections (also auto-added by the plugin). */\n collections: CollectionConfig[];\n /** Resolved configuration with all defaults applied. */\n config: ResolvedFormBuilderConfig;\n}\n\n/**\n * Create a Form Builder plugin instance.\n *\n * @example Basic usage\n * ```ts\n * import { defineConfig } from \"nextly/config\";\n * import { formBuilder } from \"@nextlyhq/plugin-form-builder\";\n *\n * const fb = formBuilder();\n *\n * export default defineConfig({\n * plugins: [fb.plugin],\n * });\n * ```\n *\n * @example With options\n * ```ts\n * const fb = formBuilder({\n * notifications: { defaultFrom: \"noreply@example.com\" },\n * spamProtection: { honeypot: true },\n * formOverrides: {\n * slug: \"contact-forms\",\n * labels: { singular: \"Contact Form\", plural: \"Contact Forms\" },\n * },\n * });\n * ```\n */\nexport function formBuilder(\n options: FormBuilderPluginOptions = {}\n): FormBuilderPluginResult {\n const resolvedConfig = resolveConfig(options);\n\n const formsCol = formsCollection(resolvedConfig);\n const submissionsCol = submissionsCollection(resolvedConfig);\n\n const plugin: NextlyPlugin = {\n name: \"@nextlyhq/plugin-form-builder\",\n version: \"0.0.8\",\n collections: [formsCol, submissionsCol],\n\n admin: {\n order: 50,\n description: \"Create and manage forms with submission tracking\",\n },\n\n // -- Config transformer --------------------------------------------------\n // Automatically adds plugin collections so users don't have to spread them.\n config(config: Parameters<NonNullable<NextlyPlugin[\"config\"]>>[0]) {\n const existing: CollectionConfig[] = config.collections || [];\n const formsSlug = resolvedConfig.formOverrides.slug;\n const submissionsSlug = resolvedConfig.formSubmissionOverrides.slug;\n\n const toAdd: CollectionConfig[] = [];\n if (!existing.some((c: CollectionConfig) => c.slug === formsSlug))\n toAdd.push(formsCol);\n if (!existing.some((c: CollectionConfig) => c.slug === submissionsSlug))\n toAdd.push(submissionsCol);\n\n return { ...config, collections: [...existing, ...toAdd] };\n },\n\n // -- Init ----------------------------------------------------------------\n // Registers an afterCreate hook on submissions to send email notifications.\n init(nextly: NextlyInstance) {\n const submissionSlug = resolvedConfig.formSubmissionOverrides.slug;\n\n // Prevent duplicate hook registration in Next.js dev mode\n const guardKey = `__formBuilder_afterCreate_${submissionSlug}`;\n const g = globalThis as Record<string, unknown>;\n if (g[guardKey]) return;\n g[guardKey] = true;\n\n nextly.infra.logger.info(\"Form Builder plugin initialized\", {\n formsCollection: resolvedConfig.formOverrides.slug,\n submissionsCollection: submissionSlug,\n enabledFields: Object.entries(resolvedConfig.fields)\n .filter(([, on]) => on)\n .map(([name]) => name),\n });\n\n (nextly as NextlyWithFormBuilderConfig).__formBuilderConfig =\n resolvedConfig;\n\n // Register afterCreate hook for email notifications\n nextly.hooks.on(\n \"afterCreate\",\n submissionSlug,\n async (context: unknown) => {\n await handleSubmissionCreated(context, resolvedConfig, nextly);\n }\n );\n },\n };\n\n return {\n plugin,\n collections: [formsCol, submissionsCol],\n config: resolvedConfig,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Utilities\n// ---------------------------------------------------------------------------\n\n/**\n * Retrieve the resolved Form Builder config from a Nextly plugin context.\n */\nexport function getFormBuilderConfig(\n nextly: unknown\n): ResolvedFormBuilderConfig | undefined {\n if (!nextly || typeof nextly !== \"object\") return undefined;\n return (nextly as Partial<NextlyWithFormBuilderConfig>).__formBuilderConfig;\n}\n\n/**\n * Resolve a `{{fieldName}}` template reference against submitted values.\n * Plain strings (e.g. email addresses) are returned as-is.\n */\nfunction resolveFieldRef(ref: string, data: Record<string, unknown>): string {\n const match = ref.match(/^\\{\\{(\\w+)\\}\\}$/);\n if (!match) return ref;\n const value = data[match[1]];\n return typeof value === \"string\" ? value : \"\";\n}\n\n// ---------------------------------------------------------------------------\n// Attachment collection for file fields\n// ---------------------------------------------------------------------------\n\n/**\n * Scan form fields for file-upload fields marked `attachToEmail` and\n * collect the corresponding mediaIds from the submitted data. Returns\n * an array of `{ mediaId }` objects ready to pass as `attachments` to\n * `emailService.sendWithTemplate()`.\n *\n * Skips empty/missing values silently (optional file fields). Handles\n * both single-value and `multiple: true` file fields.\n *\n * @internal Exported for testing — not part of the public plugin API.\n */\nexport function collectAttachmentInputs(\n fields: Array<Record<string, unknown>>,\n submittedData: Record<string, unknown>\n): Array<{ mediaId: string }> {\n const attachments: Array<{ mediaId: string }> = [];\n\n for (const field of fields) {\n if (field.type !== \"file\" || !field.attachToEmail) continue;\n\n const name = field.name as string;\n const value = submittedData[name];\n if (value == null) continue;\n\n if (Array.isArray(value)) {\n for (const item of value) {\n if (typeof item === \"string\" && item) {\n attachments.push({ mediaId: item });\n }\n }\n } else if (typeof value === \"string\" && value) {\n attachments.push({ mediaId: value });\n }\n }\n\n return attachments;\n}\n\n// ---------------------------------------------------------------------------\n// Submission notification handler\n// ---------------------------------------------------------------------------\n\n/**\n * Send email notifications after a form submission is created.\n */\nasync function handleSubmissionCreated(\n context: unknown,\n config: ResolvedFormBuilderConfig,\n nextly: NextlyInstance\n): Promise<void> {\n const submission = (context as { data?: Record<string, unknown> }).data;\n if (!submission) return;\n\n const rawFormId = submission.form;\n let formId: string | null = null;\n if (typeof rawFormId === \"string\") {\n formId = rawFormId;\n } else if (rawFormId && typeof rawFormId === \"object\") {\n const maybeId = (rawFormId as { id?: unknown }).id;\n if (typeof maybeId === \"string\") formId = maybeId;\n }\n if (!formId) return;\n\n // Fetch the parent form\n const form = await fetchParentForm(config, formId, nextly);\n if (!form) return;\n\n const notifications = Array.isArray(form.notifications)\n ? (form.notifications as FormNotificationItem[])\n : [];\n if (notifications.length === 0) return;\n\n const emailService = nextly.services.email;\n if (!emailService) return;\n\n const submittedData = (submission.data ?? {}) as Record<string, unknown>;\n\n // Collect mediaIds from file fields marked for email attachment\n const formFields = Array.isArray(form.fields)\n ? (form.fields as Array<Record<string, unknown>>)\n : [];\n const fileAttachments = collectAttachmentInputs(formFields, submittedData);\n\n const seen = new Set<string>();\n\n for (const notification of notifications) {\n if (!notification.enabled || !notification.templateSlug) continue;\n\n // Deduplicate (UI can append duplicates on repeated saves)\n const key =\n notification.id || `${notification.to}::${notification.templateSlug}`;\n if (seen.has(key)) continue;\n seen.add(key);\n\n try {\n const to =\n notification.recipientType === \"field\"\n ? resolveFieldRef(notification.to, submittedData)\n : notification.to;\n\n if (!to) {\n nextly.infra.logger.warn?.(\n \"Form Builder: empty recipient, skipping notification\",\n { notificationId: notification.id, formSlug: form.slug }\n );\n continue;\n }\n\n const cc =\n Array.isArray(notification.cc) && notification.cc.length\n ? notification.cc\n : undefined;\n const bcc =\n Array.isArray(notification.bcc) && notification.bcc.length\n ? notification.bcc\n : undefined;\n\n await emailService.sendWithTemplate(\n notification.templateSlug,\n to,\n { ...submittedData, formName: form.name, submissionId: submission.id },\n {\n providerId: notification.providerId,\n cc,\n bcc,\n attachments: fileAttachments.length > 0 ? fileAttachments : undefined,\n }\n );\n\n nextly.infra.logger.info?.(\"Form Builder: notification sent\", {\n formSlug: form.slug,\n to,\n templateSlug: notification.templateSlug,\n attachmentCount: fileAttachments.length,\n });\n } catch (err) {\n nextly.infra.logger.error?.(\"Form Builder: notification failed\", {\n notificationId: notification.id,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n}\n\n/**\n * Fetch the parent form document for a submission.\n */\nasync function fetchParentForm(\n config: ResolvedFormBuilderConfig,\n formId: string,\n nextly: NextlyInstance\n): Promise<Record<string, unknown> | null> {\n try {\n const handler = getCollectionsHandler();\n if (!handler) {\n nextly.infra.logger.warn?.(\n \"Form Builder: CollectionsHandler unavailable, skipping notifications\"\n );\n return null;\n }\n const result = await handler.getEntry({\n collectionName: config.formOverrides.slug,\n entryId: formId,\n overrideAccess: true,\n });\n const fromData = result?.data;\n if (fromData) return fromData as Record<string, unknown>;\n const fromDoc = (result as { doc?: unknown } | undefined)?.doc;\n if (fromDoc && typeof fromDoc === \"object\") {\n return fromDoc as Record<string, unknown>;\n }\n return null;\n } catch (err) {\n nextly.infra.logger.error?.(\"Form Builder: failed to fetch form\", {\n formId,\n error: err instanceof Error ? err.message : String(err),\n });\n return null;\n }\n}\n","/**\n * Forms Collection\n *\n * Stores form definitions created via Code-First or Schema Builder.\n * Each form contains field definitions, settings, notifications, and access control.\n *\n * @module collections/forms\n * @since 0.1.0\n */\n\nimport type { CollectionConfig, FieldConfig, HookContext } from \"nextly\";\nimport {\n text,\n textarea,\n select,\n checkbox,\n group,\n json,\n relationship,\n} from \"nextly\";\n\nimport type { ResolvedFormBuilderConfig } from \"../types\";\n\n// ============================================================\n// Type Augmentation for Custom Admin Components\n// ============================================================\n// These types extend CollectionAdminOptions to support custom admin views.\n// This augmentation will be removed once nextly exports these types.\n\n/**\n * Component path string format.\n * Format: `\"package-name/path#ExportName\"`\n */\ntype ComponentPath = string;\n\n/**\n * Custom view configuration for replacing default admin views.\n */\ninterface CollectionAdminViewConfig {\n /** Component path to the custom view component */\n Component: ComponentPath;\n}\n\n/**\n * Custom components configuration for collection admin UI.\n */\ninterface CollectionAdminComponents {\n views?: {\n Edit?: CollectionAdminViewConfig;\n List?: CollectionAdminViewConfig;\n };\n BeforeListTable?: ComponentPath;\n AfterListTable?: ComponentPath;\n BeforeEdit?: ComponentPath;\n AfterEdit?: ComponentPath;\n}\n\n/**\n * Extended CollectionConfig with custom admin components support.\n * This extends the base CollectionConfig to include the components property.\n */\ninterface ExtendedCollectionConfig extends Omit<CollectionConfig, \"admin\"> {\n admin?: CollectionConfig[\"admin\"] & {\n /** Whether this collection is provided by a plugin */\n isPlugin?: boolean;\n components?: CollectionAdminComponents;\n };\n}\n\n/**\n * Generate the Forms collection configuration.\n *\n * Creates a collection for storing form definitions with fields,\n * settings, notifications, and access control.\n *\n * @param pluginConfig - Resolved plugin configuration\n * @returns ExtendedCollectionConfig for the Forms collection (with custom admin components)\n *\n * @example\n * ```typescript\n * const formsCol = formsCollection(resolvedConfig);\n * // Returns a collection with slug \"forms\" (or custom slug from config)\n * ```\n */\nexport function formsCollection(\n pluginConfig: ResolvedFormBuilderConfig\n): ExtendedCollectionConfig {\n const {\n slug,\n labels,\n fields: additionalFields,\n access: accessOverrides,\n ...overrides\n } = pluginConfig.formOverrides;\n\n // Build settings group fields based on plugin configuration\n const settingsFields: FieldConfig[] = [\n text({\n name: \"submitButtonText\",\n label: \"Submit Button Text\",\n defaultValue: \"Submit\",\n admin: {\n description: \"Text displayed on the form submit button\",\n },\n }),\n\n select({\n name: \"confirmationType\",\n label: \"After Submission\",\n defaultValue: \"message\",\n options: [\n { label: \"Show Message\", value: \"message\" },\n { label: \"Redirect to URL\", value: \"redirect\" },\n ...(pluginConfig.redirectRelationships.length > 0\n ? [{ label: \"Redirect to Page\", value: \"relationship\" }]\n : []),\n ],\n admin: {\n description: \"What happens after a successful form submission\",\n },\n }),\n\n textarea({\n name: \"successMessage\",\n label: \"Success Message\",\n defaultValue: \"Thank you for your submission!\",\n admin: {\n description: \"Message shown after successful submission\",\n condition: {\n field: \"settings.confirmationType\",\n equals: \"message\",\n },\n },\n }),\n\n text({\n name: \"redirectUrl\",\n label: \"Redirect URL\",\n admin: {\n description: \"URL to redirect to after submission\",\n condition: {\n field: \"settings.confirmationType\",\n equals: \"redirect\",\n },\n },\n }),\n ];\n\n // Add redirect relationship field if configured\n if (pluginConfig.redirectRelationships.length > 0) {\n settingsFields.push(\n relationship({\n name: \"redirectPage\",\n label: \"Redirect Page\",\n relationTo: pluginConfig.redirectRelationships,\n admin: {\n description: \"Select a page to redirect to after submission\",\n condition: {\n field: \"settings.confirmationType\",\n equals: \"relationship\",\n },\n },\n })\n );\n }\n\n // Add remaining settings fields\n settingsFields.push(\n checkbox({\n name: \"allowMultipleSubmissions\",\n label: \"Allow Multiple Submissions\",\n defaultValue: true,\n admin: {\n description:\n \"Allow the same user/IP to submit this form multiple times\",\n },\n }),\n\n checkbox({\n name: \"captchaEnabled\",\n label: \"Enable reCAPTCHA\",\n defaultValue: false,\n admin: {\n description: \"Protect this form with Google reCAPTCHA v3\",\n },\n }),\n\n text({\n name: \"captchaSiteKey\",\n label: \"reCAPTCHA Site Key\",\n admin: {\n description: \"Override the default reCAPTCHA site key for this form\",\n condition: {\n field: \"settings.captchaEnabled\",\n equals: true,\n },\n },\n })\n );\n\n // Build the default fields array\n const defaultFields: FieldConfig[] = [\n // ============================================================\n // Basic Information\n // ============================================================\n text({\n name: \"name\",\n label: \"Form Name\",\n required: true,\n admin: {\n description: \"Internal name for this form (not shown to users)\",\n },\n }),\n\n text({\n name: \"slug\",\n label: \"Slug\",\n required: true,\n unique: true,\n admin: {\n description: \"URL-friendly identifier. Used in API: /api/forms/{slug}\",\n },\n }),\n\n textarea({\n name: \"description\",\n label: \"Description\",\n admin: {\n description: \"Internal notes about this form's purpose\",\n },\n }),\n\n // ============================================================\n // Form Fields (JSON schema managed by Form Builder UI)\n // ============================================================\n json({\n name: \"fields\",\n label: \"Form Fields\",\n required: true,\n defaultValue: [],\n admin: {\n description:\n \"Form field configuration. Managed by the Form Builder UI.\",\n },\n }),\n\n // ============================================================\n // Form Settings\n // ============================================================\n group({\n name: \"settings\",\n label: \"Form Settings\",\n fields: settingsFields,\n }),\n\n // ============================================================\n // Email Notifications\n // ============================================================\n json({\n name: \"notifications\",\n label: \"Email Integrations\",\n defaultValue: [],\n admin: {\n description:\n \"Email notification integrations. Managed by the Form Builder UI.\",\n },\n }),\n\n // ============================================================\n // Status\n // ============================================================\n select({\n name: \"status\",\n label: \"Status\",\n required: true,\n defaultValue: \"draft\",\n options: [\n { label: \"Draft\", value: \"draft\" },\n { label: \"Published\", value: \"published\" },\n { label: \"Closed\", value: \"closed\" },\n ],\n admin: {\n description:\n \"Only published forms can receive submissions. Closed forms display a message instead.\",\n },\n }),\n\n textarea({\n name: \"closedMessage\",\n label: \"Closed Form Message\",\n defaultValue: \"This form is no longer accepting submissions.\",\n admin: {\n description: \"Message shown when form status is 'closed'\",\n condition: {\n field: \"status\",\n equals: \"closed\",\n },\n },\n }),\n ];\n\n // Process additional fields from formOverrides\n // Note: Function-style overrides should be resolved in plugin.ts before reaching here\n let finalFields: FieldConfig[];\n\n if (Array.isArray(additionalFields) && additionalFields.length > 0) {\n // Merge: append additional fields to defaults\n finalFields = [...defaultFields, ...additionalFields];\n } else {\n finalFields = defaultFields;\n }\n\n // Convert hyphenated slug to underscored table name for database compatibility\n const dbName = slug.replace(/-/g, \"_\");\n\n return {\n slug,\n dbName,\n labels,\n fields: finalFields,\n timestamps: true,\n\n admin: {\n isPlugin: true,\n group: \"Forms\",\n order: 50,\n useAsTitle: \"name\",\n description:\n \"Create and manage your form templates using the visual form builder. Create and manage forms for collecting user submissions\",\n\n // Custom components for visual form builder\n components: {\n views: {\n // Replace default edit view with visual Form Builder\n Edit: {\n Component: \"@nextlyhq/plugin-form-builder/admin#FormBuilderView\",\n },\n },\n },\n\n ...overrides.admin,\n },\n\n // Access control with sensible defaults\n access: {\n // Anyone can read forms (needed for frontend rendering)\n read: accessOverrides?.read ?? true,\n // Only authenticated users can create/update forms\n create: accessOverrides?.create ?? (({ user }) => !!user),\n update: accessOverrides?.update ?? (({ user }) => !!user),\n // Only admins can delete forms (falls back to DB permissions)\n delete:\n accessOverrides?.delete ??\n (({ roles }) =>\n roles.includes(\"admin\") || roles.includes(\"super-admin\")),\n },\n\n hooks: {\n // Auto-generate slug from name if not provided\n beforeValidate: [\n (context: HookContext) => {\n const { data, operation } = context;\n\n // Auto-generate slug from name if not provided (only on create)\n if (operation === \"create\" && data && !data.slug && data.name) {\n data.slug = String(data.name)\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n }\n\n // Validation: Ensure fields array is not empty\n // We check for length 0 to prevent empty forms from being saved\n if (\n data &&\n (data.fields === undefined ||\n (Array.isArray(data.fields) && data.fields.length === 0))\n ) {\n throw new Error(\"Form must have at least one field.\");\n }\n\n return data;\n },\n ],\n\n // Add virtual submission count field\n afterRead: [\n (context: HookContext) => {\n const { data } = context;\n if (data) {\n (data as Record<string, unknown>).submissionCount = 0;\n }\n return data;\n },\n ],\n },\n\n // Spread any additional overrides (excluding already used properties)\n ...overrides,\n };\n}\n","/**\n * Form Submissions Collection\n *\n * Stores form submission data from user-submitted forms.\n * Each submission contains the submitted data, metadata, and status.\n *\n * @module collections/submissions\n * @since 0.1.0\n */\n\nimport type { CollectionConfig, FieldConfig, HookContext } from \"nextly\";\nimport { text, textarea, select, json, date, relationship } from \"nextly\";\n\nimport type { ResolvedFormBuilderConfig } from \"../types\";\n\n/**\n * Extended CollectionConfig with isPlugin support.\n * This extends the base CollectionConfig to include the isPlugin property.\n */\ninterface ExtendedCollectionConfig extends Omit<CollectionConfig, \"admin\"> {\n admin?: CollectionConfig[\"admin\"] & {\n /** Whether this collection is provided by a plugin */\n isPlugin?: boolean;\n };\n}\n\n/**\n * Generate the Form Submissions collection configuration.\n *\n * Creates a collection for storing form submissions with:\n * - Reference to the parent form\n * - Submitted data (JSON)\n * - Status tracking (new, read, archived)\n * - Metadata (IP, user agent, timestamp)\n * - Internal notes for admin use\n *\n * @param pluginConfig - Resolved plugin configuration\n * @returns CollectionConfig for the Submissions collection\n *\n * @example\n * ```typescript\n * const submissionsCol = submissionsCollection(resolvedConfig);\n * // Returns a collection with slug \"form-submissions\" (or custom slug from config)\n * ```\n */\nexport function submissionsCollection(\n pluginConfig: ResolvedFormBuilderConfig\n): ExtendedCollectionConfig {\n const {\n slug,\n labels,\n fields: additionalFields,\n access: accessOverrides,\n ...overrides\n } = pluginConfig.formSubmissionOverrides;\n\n // Get the forms collection slug for the relationship\n const formSlug = pluginConfig.formOverrides.slug;\n\n // Build the default fields array\n const defaultFields: FieldConfig[] = [\n // ============================================================\n // Form Relationship\n // ============================================================\n relationship({\n name: \"form\",\n label: \"Form\",\n relationTo: formSlug,\n required: true,\n admin: {\n readOnly: true,\n description: \"The form this submission belongs to\",\n },\n }),\n\n // ============================================================\n // Submission Data (JSON)\n // ============================================================\n json({\n name: \"data\",\n label: \"Submission Data\",\n required: true,\n admin: {\n readOnly: true,\n description: \"The submitted form data as JSON\",\n },\n }),\n\n // ============================================================\n // Status\n // ============================================================\n select({\n name: \"status\",\n label: \"Status\",\n required: true,\n defaultValue: \"new\",\n options: [\n { label: \"New\", value: \"new\" },\n { label: \"Read\", value: \"read\" },\n { label: \"Archived\", value: \"archived\" },\n ],\n admin: {\n description: \"Track whether this submission has been reviewed\",\n },\n }),\n\n // ============================================================\n // Internal Notes\n // ============================================================\n textarea({\n name: \"notes\",\n label: \"Internal Notes\",\n admin: {\n description: \"Notes visible only to admins (not shown to submitter)\",\n },\n }),\n\n // ============================================================\n // Metadata\n // ============================================================\n text({\n name: \"ipAddress\",\n label: \"IP Address\",\n admin: {\n readOnly: true,\n description: \"IP address of the submitter\",\n },\n }),\n\n text({\n name: \"userAgent\",\n label: \"User Agent\",\n admin: {\n readOnly: true,\n description: \"Browser/device information of the submitter\",\n },\n }),\n\n date({\n name: \"submittedAt\",\n label: \"Submitted At\",\n required: true,\n admin: {\n readOnly: true,\n description: \"When the form was submitted\",\n },\n }),\n ];\n\n // Process additional fields from formSubmissionOverrides\n // Note: Function-style overrides should be resolved in plugin.ts before reaching here\n let finalFields: FieldConfig[];\n\n if (Array.isArray(additionalFields) && additionalFields.length > 0) {\n // Merge: append additional fields to defaults\n finalFields = [...defaultFields, ...additionalFields];\n } else {\n finalFields = defaultFields;\n }\n\n // Convert hyphenated slug to underscored table name (e.g., \"form-submissions\" -> \"form_submissions\")\n const dbName = slug.replace(/-/g, \"_\");\n\n return {\n slug,\n dbName,\n labels,\n fields: finalFields,\n timestamps: true,\n\n admin: {\n isPlugin: true,\n group: \"Forms\",\n order: 51,\n useAsTitle: \"id\",\n description: \"View and manage form submissions\",\n\n // Custom components for submissions list\n components: {\n // Filter dropdown to filter submissions by form\n BeforeListTable:\n \"@nextlyhq/plugin-form-builder/admin#SubmissionsFilter\",\n },\n\n ...overrides.admin,\n },\n\n // Access control with sensible defaults\n // Public can create (submit forms), but only authenticated users can read/update/delete\n access: {\n // Anyone can submit forms (public access)\n create: accessOverrides?.create ?? true,\n // Only authenticated users can view submissions\n read: accessOverrides?.read ?? (({ user }) => !!user),\n // Only authenticated users can update submissions (e.g., change status, add notes)\n update: accessOverrides?.update ?? (({ user }) => !!user),\n // Only admins can delete submissions (falls back to DB permissions)\n delete:\n accessOverrides?.delete ??\n (({ roles }) =>\n roles.includes(\"admin\") || roles.includes(\"super-admin\")),\n },\n\n hooks: {\n // Auto-set submittedAt timestamp on creation\n beforeValidate: [\n (context: HookContext) => {\n const { data, operation } = context;\n if (operation === \"create\" && data && !data.submittedAt) {\n data.submittedAt = new Date();\n }\n return data;\n },\n ],\n\n afterChange: [\n (context: HookContext) => {\n const { data } = context;\n return data;\n },\n ],\n },\n\n // Spread any additional overrides (excluding already used properties)\n ...overrides,\n };\n}\n","/**\n * Form Builder Plugin Types\n *\n * Defines configuration options and types for the form builder plugin.\n *\n * @packageDocumentation\n */\n\nimport type { CollectionConfig, FieldDefinition, RequestContext } from \"nextly\";\nimport type { ComponentType } from \"react\";\n\n// ============================================================\n// Supporting Types\n// ============================================================\n\n/**\n * Configuration for customizing field block behavior.\n */\nexport interface FieldBlockConfig {\n /** Default label for this field type */\n label?: string;\n /** Default validation settings */\n validation?: Record<string, unknown>;\n /** Custom component to use for this field type */\n component?: ComponentType<unknown>;\n}\n\n/**\n * Email configuration for notifications.\n */\nexport interface EmailConfig {\n to: string | string[];\n from?: string;\n subject: string;\n html?: string;\n text?: string;\n replyTo?: string;\n cc?: string | string[];\n bcc?: string | string[];\n}\n\n/**\n * A single email notification integration stored on a form.\n * Matches the shape saved by the form builder admin UI.\n */\nexport interface FormNotificationItem {\n /** Unique ID for this notification */\n id: string;\n /** Display name for this integration */\n name: string;\n /** Whether this notification is active */\n enabled: boolean;\n /** ID of the email provider to use; undefined = system default */\n providerId?: string;\n /** Sender email override; undefined = use provider's configured address */\n senderEmail?: string;\n /** How the recipient address is determined */\n recipientType: \"static\" | \"field\";\n /** Recipient: static email address or a {{fieldName}} reference */\n to: string;\n /** CC email addresses */\n cc: string[];\n /** BCC email addresses */\n bcc: string[];\n /** Slug of the email template to use */\n templateSlug?: string;\n}\n\n/**\n * Form document structure (stored in database).\n */\nexport interface FormDocument {\n id: string;\n slug: string;\n name: string;\n fields: FormField[];\n settings: FormSettings;\n notifications: FormNotificationItem[];\n webhooks?: WebhookConfig[];\n status: \"draft\" | \"published\" | \"closed\";\n createdAt: Date;\n updatedAt: Date;\n}\n\n/**\n * Submission document structure (stored in database).\n */\nexport interface SubmissionDocument {\n id: string;\n form: string | FormDocument;\n data: Record<string, unknown>;\n status: \"new\" | \"read\" | \"archived\";\n ipAddress?: string;\n userAgent?: string;\n submittedAt: Date;\n createdAt: Date;\n updatedAt: Date;\n}\n\n// ============================================================\n// Plugin Configuration\n// ============================================================\n\n/**\n * Configuration options for the form builder plugin.\n */\nexport interface FormBuilderPluginOptions {\n /**\n * Override the default Forms collection configuration.\n *\n * Allows customizing the slug, labels, fields, access control, and other\n * collection options.\n *\n * The `fields` option can be:\n * - An array of fields (merged with defaults)\n * - A function receiving `{ defaultFields }` for full control\n *\n * @example\n * ```typescript\n * // Option 1: Array (fields merged with defaults)\n * formOverrides: {\n * slug: 'my-forms',\n * labels: { singular: 'My Form', plural: 'My Forms' },\n * access: {\n * read: () => true,\n * create: ({ roles }) => roles.includes('admin'),\n * },\n * fields: [\n * text({ name: 'internalNotes', label: 'Internal Notes' }),\n * ],\n * }\n *\n * // Option 2: Function (full control over fields)\n * formOverrides: {\n * slug: 'contact-forms',\n * access: {\n * read: ({ user }) => !!user,\n * update: () => false,\n * },\n * fields: ({ defaultFields }) => {\n * return [\n * ...defaultFields,\n * { name: 'custom', type: 'text' },\n * ];\n * },\n * }\n * ```\n */\n formOverrides?: Partial<CollectionConfig> & {\n slug?: string;\n labels?: {\n singular?: string;\n plural?: string;\n };\n /** Fields can be an array or a function receiving defaultFields */\n fields?:\n | FieldDefinition[]\n | ((args: { defaultFields: FieldDefinition[] }) => FieldDefinition[]);\n };\n\n /**\n * Override the default Form Submissions collection configuration.\n *\n * Allows customizing the slug, labels, fields, access control, and other\n * collection options.\n *\n * The `fields` option can be:\n * - An array of fields (merged with defaults)\n * - A function receiving `{ defaultFields }` for full control\n *\n * @example\n * ```typescript\n * // Option 1: Array (fields merged with defaults)\n * formSubmissionOverrides: {\n * slug: 'my-submissions',\n * labels: { singular: 'Response', plural: 'Responses' },\n * access: {\n * read: ({ roles }) => roles.includes('admin'),\n * },\n * fields: [\n * select({\n * name: 'priority',\n * label: 'Priority',\n * options: [\n * { label: 'Low', value: 'low' },\n * { label: 'High', value: 'high' },\n * ],\n * }),\n * ],\n * }\n *\n * // Option 2: Function (full control over fields)\n * formSubmissionOverrides: {\n * fields: ({ defaultFields }) => {\n * // Add field after 'form' field\n * const formIndex = defaultFields.findIndex(f => f.name === 'form');\n * return [\n * ...defaultFields.slice(0, formIndex + 1),\n * { name: 'source', type: 'text' },\n * ...defaultFields.slice(formIndex + 1),\n * ];\n * },\n * }\n * ```\n */\n formSubmissionOverrides?: Partial<CollectionConfig> & {\n slug?: string;\n labels?: {\n singular?: string;\n plural?: string;\n };\n /** Fields can be an array or a function receiving defaultFields */\n fields?:\n | FieldDefinition[]\n | ((args: { defaultFields: FieldDefinition[] }) => FieldDefinition[]);\n };\n\n /**\n * @deprecated Use `formOverrides` and `formSubmissionOverrides` instead.\n * This option is kept for backward compatibility.\n */\n collections?: {\n forms?: Partial<CollectionConfig> & {\n slug?: string;\n labels?: {\n singular?: string;\n plural?: string;\n };\n };\n submissions?: Partial<CollectionConfig> & {\n slug?: string;\n labels?: {\n singular?: string;\n plural?: string;\n };\n };\n };\n\n /**\n * Configure which field types are available in the form builder.\n *\n * Set to `false` to disable a field type, or provide partial configuration\n * to customize default behavior.\n *\n * @example\n * ```typescript\n * fields: {\n * // Disable payment field\n * payment: false,\n *\n * // Customize text field defaults\n * text: {\n * maxLength: 500,\n * },\n *\n * // Disable file uploads\n * file: false,\n * }\n * ```\n */\n fields?: {\n text?: boolean | Partial<FieldBlockConfig>;\n email?: boolean | Partial<FieldBlockConfig>;\n number?: boolean | Partial<FieldBlockConfig>;\n phone?: boolean | Partial<FieldBlockConfig>;\n url?: boolean | Partial<FieldBlockConfig>;\n textarea?: boolean | Partial<FieldBlockConfig>;\n select?: boolean | Partial<FieldBlockConfig>;\n checkbox?: boolean | Partial<FieldBlockConfig>;\n radio?: boolean | Partial<FieldBlockConfig>;\n file?: boolean | Partial<FieldBlockConfig>;\n date?: boolean | Partial<FieldBlockConfig>;\n time?: boolean | Partial<FieldBlockConfig>;\n hidden?: boolean | Partial<FieldBlockConfig>;\n };\n\n /**\n * Collections that can be used as redirect targets after form submission.\n *\n * When specified, the form builder will allow selecting documents from\n * these collections as redirect destinations.\n *\n * @example\n * ```typescript\n * redirectRelationships: ['pages', 'posts', 'landing-pages']\n * ```\n */\n redirectRelationships?: string[];\n\n /**\n * Hook called before sending email notifications.\n *\n * Allows modifying, adding, or filtering emails before they are sent.\n * Return the modified emails array.\n *\n * @example\n * ```typescript\n * beforeEmail: async ({ emails, form, submission }) => {\n * // Add BCC to all emails\n * return emails.map(email => ({\n * ...email,\n * bcc: ['archive@example.com'],\n * }));\n * }\n * ```\n */\n beforeEmail?: (args: {\n emails: EmailConfig[];\n form: FormDocument;\n submission: SubmissionDocument;\n }) => Promise<EmailConfig[]> | EmailConfig[];\n\n /**\n * Email notification settings.\n */\n notifications?: {\n /**\n * Default \"from\" email address.\n */\n defaultFrom?: string;\n\n /**\n * Default \"to\" email address for all form submissions.\n *\n * If specified, this email will receive all form submissions\n * regardless of form-specific notification settings.\n * @example\n * ```typescript\n * notifications: {\n * defaultToEmail: 'submissions@example.com',\n * }\n * ```\n */\n defaultToEmail?: string;\n\n /**\n * Enable/disable email notifications globally.\n * @default true\n */\n enabled?: boolean;\n };\n\n /**\n * Spam protection configuration.\n */\n spamProtection?: {\n /**\n * Enable honeypot field for spam detection.\n * @default true\n */\n honeypot?: boolean;\n\n /**\n * Enable reCAPTCHA v3.\n * @default false\n */\n recaptcha?: {\n enabled: boolean;\n siteKey?: string;\n secretKey?: string;\n scoreThreshold?: number; // 0.0 - 1.0\n };\n\n /**\n * Rate limiting configuration.\n */\n rateLimit?: {\n /**\n * Maximum submissions per window.\n * @default 10\n */\n maxSubmissions?: number;\n\n /**\n * Time window in milliseconds.\n * @default 60000 (1 minute)\n */\n windowMs?: number;\n };\n };\n\n /**\n * File upload settings.\n */\n uploads?: {\n /**\n * Maximum file size in bytes.\n * @default 10485760 (10MB)\n */\n maxFileSize?: number;\n\n /**\n * Allowed file types (MIME types).\n * @default ['image/*', 'application/pdf', 'text/*']\n */\n allowedMimeTypes?: string[];\n\n /**\n * Collection to store uploaded files.\n * @default 'media'\n */\n uploadCollection?: string;\n };\n\n /**\n * Enable/disable features.\n */\n features?: {\n /**\n * Enable visual form builder UI.\n * @default true\n */\n builder?: boolean;\n\n /**\n * Enable conditional logic.\n * @default true\n */\n conditionalLogic?: boolean;\n\n /**\n * Enable file uploads.\n * @default true\n */\n fileUploads?: boolean;\n };\n}\n\n// ============================================================\n// Form Configuration Types\n// ============================================================\n\n/**\n * Form configuration interface.\n * Similar to CollectionConfig but tailored for forms.\n */\nexport interface FormConfig {\n /**\n * Unique identifier for the form.\n */\n slug: string;\n\n /**\n * Display labels for the form.\n */\n labels?: {\n singular?: string;\n plural?: string;\n };\n\n /**\n * Form fields definition.\n */\n fields: FormField[];\n\n /**\n * Form settings and behavior.\n */\n settings?: FormSettings;\n\n /**\n * Email notification configuration.\n */\n notifications?: FormNotifications;\n\n /**\n * Access control for form submission and viewing.\n */\n access?: FormAccess;\n\n /**\n * Lifecycle hooks for form submissions.\n */\n hooks?: FormHooks;\n\n /**\n * Custom metadata.\n */\n custom?: Record<string, unknown>;\n}\n\n// ============================================================\n// Form Field Types (Discriminated Union)\n// ============================================================\n\n/**\n * Form field type (discriminated union).\n * Uses the `type` property as the discriminant.\n */\nexport type FormField =\n | TextFormField\n | EmailFormField\n | NumberFormField\n | PhoneFormField\n | UrlFormField\n | TextareaFormField\n | SelectFormField\n | CheckboxFormField\n | RadioFormField\n | FileFormField\n | DateFormField\n | TimeFormField\n | HiddenFormField;\n\n/**\n * All available form field type identifiers.\n */\nexport type FormFieldType =\n | \"text\"\n | \"email\"\n | \"number\"\n | \"phone\"\n | \"url\"\n | \"textarea\"\n | \"select\"\n | \"checkbox\"\n | \"radio\"\n | \"file\"\n | \"date\"\n | \"time\"\n | \"hidden\";\n\n/**\n * Base form field properties.\n * All form field types extend this interface.\n */\nexport interface BaseFormField {\n /** Field type identifier (discriminant) */\n type: FormFieldType;\n\n /** Field name (used as key in submission data) */\n name: string;\n\n /** Display label */\n label: string;\n\n /** Placeholder text */\n placeholder?: string;\n\n /** Help text shown below field */\n helpText?: string;\n\n /** Default value */\n defaultValue?: unknown;\n\n /** Whether field is required */\n required?: boolean;\n\n /** Validation rules */\n validation?: ValidationRules;\n\n /** Conditional logic configuration */\n conditionalLogic?: ConditionalLogic;\n\n /** Admin UI configuration */\n admin?: {\n /** CSS classes */\n className?: string;\n\n /** Field width */\n width?: \"25%\" | \"33%\" | \"50%\" | \"66%\" | \"75%\" | \"100%\";\n };\n}\n\n/**\n * Text input field.\n */\nexport interface TextFormField extends Omit<BaseFormField, \"type\"> {\n type: \"text\";\n defaultValue?: string;\n validation?: ValidationRules & {\n minLength?: number;\n maxLength?: number;\n pattern?: string;\n };\n}\n\n/**\n * Email input field.\n */\nexport interface EmailFormField extends Omit<BaseFormField, \"type\"> {\n type: \"email\";\n defaultValue?: string;\n validation?: ValidationRules & {\n pattern?: string;\n };\n}\n\n/**\n * Number input field.\n */\nexport interface NumberFormField extends Omit<BaseFormField, \"type\"> {\n type: \"number\";\n defaultValue?: number;\n validation?: ValidationRules & {\n min?: number;\n max?: number;\n step?: number;\n };\n}\n\n/**\n * Phone input field.\n */\nexport interface PhoneFormField extends Omit<BaseFormField, \"type\"> {\n type: \"phone\";\n defaultValue?: string;\n validation?: ValidationRules & {\n pattern?: string;\n };\n}\n\n/**\n * URL input field.\n */\nexport interface UrlFormField extends Omit<BaseFormField, \"type\"> {\n type: \"url\";\n defaultValue?: string;\n validation?: ValidationRules & {\n pattern?: string;\n };\n}\n\n/**\n * Textarea field.\n */\nexport interface TextareaFormField extends Omit<BaseFormField, \"type\"> {\n type: \"textarea\";\n defaultValue?: string;\n rows?: number;\n validation?: ValidationRules & {\n minLength?: number;\n maxLength?: number;\n };\n}\n\n/**\n * Select dropdown field.\n */\nexport interface SelectFormField extends Omit<BaseFormField, \"type\"> {\n type: \"select\";\n options: Array<{ label: string; value: string }>;\n defaultValue?: string | string[];\n allowMultiple?: boolean;\n}\n\n/**\n * Single checkbox field.\n */\nexport interface CheckboxFormField extends Omit<BaseFormField, \"type\"> {\n type: \"checkbox\";\n defaultValue?: boolean;\n}\n\n/**\n * Radio group field.\n */\nexport interface RadioFormField extends Omit<BaseFormField, \"type\"> {\n type: \"radio\";\n options: Array<{ label: string; value: string }>;\n defaultValue?: string;\n}\n\n/**\n * File upload field.\n */\nexport interface FileFormField extends Omit<BaseFormField, \"type\"> {\n type: \"file\";\n accept?: string; // MIME types\n multiple?: boolean;\n maxFileSize?: number; // bytes\n /**\n * When true, files uploaded through this field are attached to all\n * notification emails for this form. Default false.\n */\n attachToEmail?: boolean;\n}\n\n/**\n * Date picker field.\n */\nexport interface DateFormField extends Omit<BaseFormField, \"type\"> {\n type: \"date\";\n defaultValue?: string; // ISO date string\n min?: string; // ISO date\n max?: string; // ISO date\n}\n\n/**\n * Time picker field.\n */\nexport interface TimeFormField extends Omit<BaseFormField, \"type\"> {\n type: \"time\";\n defaultValue?: string; // HH:mm format\n}\n\n/**\n * Hidden field.\n */\nexport interface HiddenFormField extends Omit<BaseFormField, \"type\"> {\n type: \"hidden\";\n defaultValue?: string;\n}\n\n// ============================================================\n// Validation Types\n// ============================================================\n\n/**\n * Validation rules for form fields.\n */\nexport interface ValidationRules {\n /** Custom error message */\n errorMessage?: string;\n\n /** Custom validation function */\n validate?: (\n value: unknown,\n formData: Record<string, unknown>\n ) => boolean | string | Promise<boolean | string>;\n}\n\n// ============================================================\n// Conditional Logic Types\n// ============================================================\n\n/**\n * Conditional logic configuration.\n */\nexport interface ConditionalLogic {\n /** Whether conditional logic is enabled */\n enabled: boolean;\n\n /** Action to perform when conditions are met */\n action: \"show\" | \"hide\";\n\n /** Logical operator for multiple conditions */\n operator: \"AND\" | \"OR\";\n\n /** Conditions to evaluate */\n conditions: ConditionalLogicCondition[];\n}\n\n/**\n * A single conditional logic condition.\n */\nexport interface ConditionalLogicCondition {\n /** Field name to check */\n field: string;\n\n /** Comparison operator */\n comparison:\n | \"equals\"\n | \"notEquals\"\n | \"contains\"\n | \"isEmpty\"\n | \"isNotEmpty\"\n | \"greaterThan\"\n | \"lessThan\";\n\n /** Value to compare against (not used for isEmpty/isNotEmpty) */\n value?: unknown;\n}\n\n// ============================================================\n// Form Settings Types\n// ============================================================\n\n/**\n * Form settings and behavior.\n */\nexport interface FormSettings {\n /** Submit button text */\n submitButtonText?: string;\n\n /**\n * Confirmation type after successful submission.\n * - \"message\": Display a success message\n * - \"redirect\": Redirect to a URL or linked document\n *\n * @default \"message\"\n */\n confirmationType?: \"message\" | \"redirect\";\n\n /** Success message (supports HTML) - used when confirmationType is \"message\" */\n successMessage?: string;\n\n /** Redirect URL after successful submission - used when confirmationType is \"redirect\" */\n redirectUrl?: string;\n\n /**\n * Relationship to redirect document - used when confirmationType is \"redirect\"\n * and redirectRelationships are configured in plugin options.\n */\n redirectRelation?: {\n relationTo: string;\n value: string; // Document ID\n };\n\n /** Allow multiple submissions from same user/IP */\n allowMultipleSubmissions?: boolean;\n\n /** reCAPTCHA configuration */\n captcha?: {\n enabled: boolean;\n siteKey?: string;\n };\n}\n\n// ============================================================\n// Notification Types\n// ============================================================\n\n/**\n * Email notification configuration for a form.\n */\nexport interface FormNotifications {\n /** Enable email notifications */\n enabled: boolean;\n\n /** Recipient email addresses */\n recipients: Array<{\n email: string;\n name?: string;\n }>;\n\n /** Email subject line */\n subject?: string;\n\n /** Use submitter's email as reply-to */\n replyTo?: boolean;\n\n /** Custom email template */\n template?: string; // HTML template\n\n /**\n * Webhook endpoints to notify on form events.\n *\n * Webhooks are fired asynchronously and do not block form submission.\n * Failed webhooks are logged but do not cause submission failures.\n *\n * @example\n * ```typescript\n * webhooks: [\n * {\n * url: 'https://api.example.com/webhooks/form-submissions',\n * events: ['submission.created'],\n * secret: 'your-webhook-secret', // Optional HMAC signature\n * },\n * {\n * url: 'https://crm.example.com/api/leads',\n * events: ['submission.created', 'submission.updated'],\n * headers: { 'X-API-Key': 'abc123' },\n * includeData: true,\n * },\n * ]\n * ```\n */\n webhooks?: WebhookConfig[];\n}\n\n// ============================================================\n// Webhook Types\n// ============================================================\n\n/**\n * Webhook event types.\n */\nexport type WebhookEvent =\n | \"submission.created\"\n | \"submission.updated\"\n | \"submission.deleted\";\n\n/**\n * Configuration for a webhook endpoint.\n */\nexport interface WebhookConfig {\n /** Webhook URL to send POST requests to */\n url: string;\n\n /**\n * HTTP method for the webhook request.\n * @default \"POST\"\n */\n method?: \"POST\" | \"PUT\";\n\n /**\n * Custom headers to include in the webhook request.\n * Useful for authentication tokens or API keys.\n */\n headers?: Record<string, string>;\n\n /**\n * Events that trigger this webhook.\n * Must include at least one event.\n */\n events: WebhookEvent[];\n\n /**\n * Whether to include full submission data in the webhook payload.\n * If false, only submission ID and timestamp are included.\n * @default true\n */\n includeData?: boolean;\n\n /**\n * Secret key for HMAC-SHA256 signature verification.\n *\n * When provided, the webhook request will include an\n * `X-Webhook-Signature` header containing the signature:\n * `sha256=<hex-encoded-hmac>`\n *\n * Recipients can verify the signature to ensure the webhook\n * originated from your server and wasn't tampered with.\n *\n * @example\n * ```typescript\n * // Verifying the signature on the receiving end (Node.js):\n * const crypto = require('crypto');\n * const expectedSignature = req.headers['x-webhook-signature'];\n * const payload = JSON.stringify(req.body);\n * const signature = 'sha256=' + crypto\n * .createHmac('sha256', secret)\n * .update(payload)\n * .digest('hex');\n * const isValid = crypto.timingSafeEqual(\n * Buffer.from(expectedSignature),\n * Buffer.from(signature)\n * );\n * ```\n */\n secret?: string;\n}\n\n// ============================================================\n// Access Control Types\n// ============================================================\n\n/**\n * Form access control.\n */\nexport interface FormAccess {\n /** Who can submit the form */\n submit?: (context: { req: RequestContext }) => boolean | Promise<boolean>;\n\n /** Who can view submissions */\n read?: (context: { req: RequestContext }) => boolean | Promise<boolean>;\n}\n\n// ============================================================\n// Hook Types\n// ============================================================\n\n/**\n * Form lifecycle hooks.\n */\nexport interface FormHooks {\n /** Before submission validation */\n beforeValidate?: Array<(context: FormHookContext) => void | Promise<void>>;\n\n /** Before submission is saved */\n beforeSubmit?: Array<(context: FormHookContext) => void | Promise<void>>;\n\n /** After submission is saved */\n afterSubmit?: Array<(context: FormHookContext) => void | Promise<void>>;\n}\n\n/**\n * Hook context for form hooks.\n */\nexport interface FormHookContext {\n /** Form configuration */\n form: FormConfig;\n\n /** Submission data */\n data: Record<string, unknown>;\n\n /** Request context */\n req: RequestContext;\n\n /** Submission metadata */\n metadata?: {\n ipAddress?: string;\n userAgent?: string;\n };\n}\n\n// ============================================================\n// Submission Types\n// ============================================================\n\n/**\n * Form submission data structure.\n */\nexport interface FormSubmission {\n /** Submission ID */\n id: string;\n\n /** Form slug */\n formSlug: string;\n\n /** Submission data (JSON) */\n data: Record<string, unknown>;\n\n /** Submission status */\n status: \"new\" | \"read\" | \"archived\";\n\n /** Internal notes (admin only) */\n notes?: string;\n\n /** Metadata */\n ipAddress?: string;\n userAgent?: string;\n submittedAt: Date;\n\n /** Created/updated timestamps */\n createdAt: Date;\n updatedAt: Date;\n}\n\n// ============================================================\n// Resolved Configuration Types (Internal)\n// ============================================================\n\n/**\n * Resolved form builder configuration after applying defaults.\n * Used internally by the plugin.\n */\nexport interface ResolvedFormBuilderConfig {\n formOverrides: {\n slug: string;\n labels: {\n singular: string;\n plural: string;\n };\n } & Partial<CollectionConfig>;\n\n formSubmissionOverrides: {\n slug: string;\n labels: {\n singular: string;\n plural: string;\n };\n } & Partial<CollectionConfig>;\n\n fields: {\n text: boolean | Partial<FieldBlockConfig>;\n email: boolean | Partial<FieldBlockConfig>;\n number: boolean | Partial<FieldBlockConfig>;\n phone: boolean | Partial<FieldBlockConfig>;\n url: boolean | Partial<FieldBlockConfig>;\n textarea: boolean | Partial<FieldBlockConfig>;\n select: boolean | Partial<FieldBlockConfig>;\n checkbox: boolean | Partial<FieldBlockConfig>;\n radio: boolean | Partial<FieldBlockConfig>;\n file: boolean | Partial<FieldBlockConfig>;\n date: boolean | Partial<FieldBlockConfig>;\n time: boolean | Partial<FieldBlockConfig>;\n hidden: boolean | Partial<FieldBlockConfig>;\n };\n\n redirectRelationships: string[];\n\n beforeEmail?: FormBuilderPluginOptions[\"beforeEmail\"];\n\n notifications: {\n defaultFrom?: string;\n defaultToEmail?: string;\n enabled: boolean;\n };\n\n spamProtection: {\n honeypot: boolean;\n recaptcha?: {\n enabled: boolean;\n siteKey?: string;\n secretKey?: string;\n scoreThreshold?: number;\n };\n rateLimit?: {\n maxSubmissions: number;\n windowMs: number;\n };\n };\n\n uploads: {\n maxFileSize: number;\n allowedMimeTypes: string[];\n uploadCollection: string;\n };\n\n features: {\n builder: boolean;\n conditionalLogic: boolean;\n fileUploads: boolean;\n };\n}\n\n// ============================================================\n// Type Guards\n// ============================================================\n\n/**\n * Check if a value is a FormField.\n */\nexport function isFormField(value: unknown): value is FormField {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"type\" in value &&\n \"name\" in value &&\n \"label\" in value\n );\n}\n\n/**\n * Check if a FormField is a specific type.\n */\nexport function isFormFieldType<T extends FormField>(\n field: FormField,\n type: T[\"type\"]\n): field is T {\n return field.type === type;\n}\n\n/**\n * Type guard for text field.\n */\nexport function isTextFormField(field: FormField): field is TextFormField {\n return field.type === \"text\";\n}\n\n/**\n * Type guard for email field.\n */\nexport function isEmailFormField(field: FormField): field is EmailFormField {\n return field.type === \"email\";\n}\n\n/**\n * Type guard for select field.\n */\nexport function isSelectFormField(field: FormField): field is SelectFormField {\n return field.type === \"select\";\n}\n\n/**\n * Type guard for checkbox field.\n */\nexport function isCheckboxFormField(\n field: FormField\n): field is CheckboxFormField {\n return field.type === \"checkbox\";\n}\n\n/**\n * Type guard for file field.\n */\nexport function isFileFormField(field: FormField): field is FileFormField {\n return field.type === \"file\";\n}\n","/**\n * Form Configuration Validator\n *\n * Provides comprehensive validation for form configurations including:\n * - Slug validation (format, reserved names)\n * - Field name validation (format, duplicates)\n * - Field-specific validation (select/radio options, min/max constraints)\n * - Conditional logic validation\n * - Nested field validation\n *\n * @module config/validate-form\n * @since 1.0.0\n *\n * @example\n * ```typescript\n * import { validateFormConfig } from '@nextly/plugin-form-builder';\n *\n * const result = validateFormConfig(config);\n * if (!result.valid) {\n * console.error('Validation errors:', result.errors);\n * }\n * ```\n */\n\nimport type { FormConfig, FormField, FormFieldType } from \"../types\";\n\n// ============================================================\n// Validation Error Types\n// ============================================================\n\n/**\n * Error codes for form validation failures.\n * Used for programmatic error handling.\n */\nexport type FormValidationErrorCode =\n // Slug errors\n | \"SLUG_REQUIRED\"\n | \"SLUG_INVALID_TYPE\"\n | \"SLUG_TOO_SHORT\"\n | \"SLUG_TOO_LONG\"\n | \"SLUG_INVALID_FORMAT\"\n | \"SLUG_RESERVED\"\n // Field errors\n | \"FIELDS_REQUIRED\"\n | \"FIELDS_INVALID_TYPE\"\n | \"FIELDS_EMPTY\"\n | \"FIELD_NAME_REQUIRED\"\n | \"FIELD_NAME_INVALID_FORMAT\"\n | \"FIELD_NAME_DUPLICATE\"\n | \"FIELD_TYPE_REQUIRED\"\n | \"FIELD_TYPE_INVALID\"\n | \"FIELD_LABEL_REQUIRED\"\n // Field-specific errors\n | \"OPTIONS_REQUIRED\"\n | \"OPTIONS_EMPTY\"\n | \"MIN_GREATER_THAN_MAX\"\n | \"MIN_LENGTH_GREATER_THAN_MAX\"\n | \"FILE_MAX_SIZE_INVALID\"\n // Conditional logic errors\n | \"CONDITIONS_REQUIRED\"\n | \"CONDITION_FIELD_REQUIRED\"\n | \"CONDITION_COMPARISON_REQUIRED\"\n // Settings errors\n | \"SETTINGS_INVALID_TYPE\"\n | \"REDIRECT_URL_REQUIRED\"\n // Access errors\n | \"ACCESS_INVALID_TYPE\"\n | \"ACCESS_FUNCTION_INVALID\";\n\n/**\n * A single validation error with path and context.\n *\n * @example\n * ```typescript\n * const error: FormValidationError = {\n * path: 'fields.email.validation',\n * message: \"Min length cannot be greater than max length\",\n * code: 'MIN_LENGTH_GREATER_THAN_MAX',\n * };\n * ```\n */\nexport interface FormValidationError {\n /**\n * Dot-notation path to the invalid property.\n * @example 'slug', 'fields.0.name', 'fields.email.options'\n */\n path: string;\n\n /**\n * Human-readable error message.\n */\n message: string;\n\n /**\n * Machine-readable error code for programmatic handling.\n */\n code: FormValidationErrorCode;\n}\n\n/**\n * Result of form config validation.\n */\nexport interface FormValidationResult {\n /**\n * Whether the configuration is valid.\n */\n valid: boolean;\n\n /**\n * Array of validation errors (empty if valid).\n */\n errors: FormValidationError[];\n}\n\n// ============================================================\n// Reserved Names\n// ============================================================\n\n/**\n * Reserved form slugs that cannot be used.\n * These are used by the system or have special meaning.\n */\nexport const RESERVED_FORM_SLUGS = [\n // API routes\n \"api\",\n \"submit\",\n \"submissions\",\n \"export\",\n // System slugs\n \"admin\",\n \"new\",\n \"create\",\n \"edit\",\n \"delete\",\n] as const;\n\n/**\n * Set for O(1) lookup of reserved slugs.\n */\nconst RESERVED_SLUGS_SET = new Set<string>(RESERVED_FORM_SLUGS);\n\n// ============================================================\n// Validation Patterns\n// ============================================================\n\n/**\n * Regex pattern for valid form slugs.\n * Must start with a lowercase letter, contain only lowercase letters,\n * numbers, hyphens, and underscores. Length: 1-50 chars.\n * Allows hyphens for URL-friendly slugs (e.g., 'contact-form').\n */\nconst SLUG_PATTERN = /^[a-z][a-z0-9_-]*$/;\n\n/**\n * Regex pattern for valid field names.\n * Must start with a letter (upper or lower), contain only letters,\n * numbers, and underscores. Supports camelCase (e.g., 'firstName').\n */\nconst FIELD_NAME_PATTERN = /^[a-zA-Z][a-zA-Z0-9_]*$/;\n\n/**\n * All valid form field types.\n */\nconst VALID_FIELD_TYPES: FormFieldType[] = [\n \"text\",\n \"email\",\n \"number\",\n \"phone\",\n \"url\",\n \"textarea\",\n \"select\",\n \"checkbox\",\n \"radio\",\n \"file\",\n \"date\",\n \"time\",\n \"hidden\",\n];\n\nconst VALID_FIELD_TYPES_SET = new Set<string>(VALID_FIELD_TYPES);\n\n// ============================================================\n// Validation Helper Functions\n// ============================================================\n\n/**\n * Checks if a string is a reserved form slug.\n */\nfunction isReservedSlug(slug: string): boolean {\n return RESERVED_SLUGS_SET.has(slug.toLowerCase());\n}\n\n/**\n * Validates a form slug.\n */\nfunction validateSlug(slug: unknown, errors: FormValidationError[]): void {\n const path = \"slug\";\n\n if (!slug) {\n errors.push({\n path,\n message: \"Form slug is required\",\n code: \"SLUG_REQUIRED\",\n });\n return;\n }\n\n if (typeof slug !== \"string\") {\n errors.push({\n path,\n message: \"Form slug must be a string\",\n code: \"SLUG_INVALID_TYPE\",\n });\n return;\n }\n\n if (slug.length < 1) {\n errors.push({\n path,\n message: \"Form slug must be at least 1 character\",\n code: \"SLUG_TOO_SHORT\",\n });\n }\n\n if (slug.length > 50) {\n errors.push({\n path,\n message: \"Form slug must be at most 50 characters\",\n code: \"SLUG_TOO_LONG\",\n });\n }\n\n if (!SLUG_PATTERN.test(slug)) {\n errors.push({\n path,\n message:\n \"Form slug must start with a lowercase letter and contain only lowercase letters, numbers, hyphens, and underscores\",\n code: \"SLUG_INVALID_FORMAT\",\n });\n }\n\n if (isReservedSlug(slug)) {\n errors.push({\n path,\n message: `Form slug '${slug}' is reserved and cannot be used`,\n code: \"SLUG_RESERVED\",\n });\n }\n}\n\n/**\n * Validates a single field name.\n */\nfunction validateFieldName(\n name: unknown,\n path: string,\n errors: FormValidationError[],\n seenNames: Set<string>\n): void {\n if (!name) {\n errors.push({\n path: `${path}.name`,\n message: \"Field name is required\",\n code: \"FIELD_NAME_REQUIRED\",\n });\n return;\n }\n\n if (typeof name !== \"string\") {\n errors.push({\n path: `${path}.name`,\n message: \"Field name must be a string\",\n code: \"FIELD_NAME_REQUIRED\",\n });\n return;\n }\n\n if (!FIELD_NAME_PATTERN.test(name)) {\n errors.push({\n path: `${path}.name`,\n message: `Field name '${name}' must start with a letter and contain only letters, numbers, and underscores`,\n code: \"FIELD_NAME_INVALID_FORMAT\",\n });\n }\n\n // Check for duplicates at this level\n const nameLower = name.toLowerCase();\n if (seenNames.has(nameLower)) {\n errors.push({\n path: `${path}.name`,\n message: `Duplicate field name '${name}'`,\n code: \"FIELD_NAME_DUPLICATE\",\n });\n } else {\n seenNames.add(nameLower);\n }\n}\n\n/**\n * Validates a single field label.\n */\nfunction validateFieldLabel(\n label: unknown,\n path: string,\n errors: FormValidationError[]\n): void {\n if (!label || (typeof label === \"string\" && label.trim() === \"\")) {\n errors.push({\n path: `${path}.label`,\n message: \"Field label is required\",\n code: \"FIELD_LABEL_REQUIRED\",\n });\n }\n}\n\n/**\n * Validates select/radio/checkbox-group field options.\n */\nfunction validateOptions(\n field: FormField,\n path: string,\n errors: FormValidationError[]\n): void {\n // Type assertion for fields with options\n const fieldWithOptions = field as {\n options?: Array<{ label: string; value: string }>;\n };\n const options = fieldWithOptions.options;\n\n if (!options) {\n errors.push({\n path: `${path}.options`,\n message: `${field.type} field must have an 'options' array`,\n code: \"OPTIONS_REQUIRED\",\n });\n return;\n }\n\n if (!Array.isArray(options)) {\n errors.push({\n path: `${path}.options`,\n message: `${field.type} field 'options' must be an array`,\n code: \"OPTIONS_REQUIRED\",\n });\n return;\n }\n\n if (options.length === 0) {\n errors.push({\n path: `${path}.options`,\n message: `${field.type} field must have at least one option`,\n code: \"OPTIONS_EMPTY\",\n });\n }\n}\n\n/**\n * Validates conditional logic configuration.\n */\nfunction validateConditionalLogic(\n field: FormField,\n path: string,\n errors: FormValidationError[]\n): void {\n const { conditionalLogic } = field;\n\n if (!conditionalLogic || !conditionalLogic.enabled) {\n return;\n }\n\n if (\n !conditionalLogic.conditions ||\n conditionalLogic.conditions.length === 0\n ) {\n errors.push({\n path: `${path}.conditionalLogic.conditions`,\n message: \"Conditional logic must have at least one condition\",\n code: \"CONDITIONS_REQUIRED\",\n });\n return;\n }\n\n conditionalLogic.conditions.forEach((condition, index) => {\n const conditionPath = `${path}.conditionalLogic.conditions[${index}]`;\n\n if (!condition.field) {\n errors.push({\n path: `${conditionPath}.field`,\n message: \"Condition must specify a field name\",\n code: \"CONDITION_FIELD_REQUIRED\",\n });\n }\n\n if (!condition.comparison) {\n errors.push({\n path: `${conditionPath}.comparison`,\n message: \"Condition must specify a comparison operator\",\n code: \"CONDITION_COMPARISON_REQUIRED\",\n });\n }\n });\n}\n\n/**\n * Validates field based on its type.\n */\nfunction validateFieldByType(\n field: FormField,\n path: string,\n errors: FormValidationError[]\n): void {\n switch (field.type) {\n case \"select\":\n case \"radio\":\n validateOptions(field, path, errors);\n break;\n\n case \"number\": {\n const numberField = field;\n if (numberField.validation) {\n const { min, max } = numberField.validation;\n if (min !== undefined && max !== undefined && min > max) {\n errors.push({\n path: `${path}.validation`,\n message: \"Min value cannot be greater than max value\",\n code: \"MIN_GREATER_THAN_MAX\",\n });\n }\n }\n break;\n }\n\n case \"text\":\n case \"textarea\": {\n const textField = field;\n if (textField.validation) {\n const { minLength, maxLength } = textField.validation;\n if (\n minLength !== undefined &&\n maxLength !== undefined &&\n minLength > maxLength\n ) {\n errors.push({\n path: `${path}.validation`,\n message: \"Min length cannot be greater than max length\",\n code: \"MIN_LENGTH_GREATER_THAN_MAX\",\n });\n }\n }\n break;\n }\n\n case \"file\": {\n const fileField = field;\n if (fileField.maxFileSize !== undefined && fileField.maxFileSize <= 0) {\n errors.push({\n path: `${path}.maxFileSize`,\n message: \"File maxFileSize must be a positive number\",\n code: \"FILE_MAX_SIZE_INVALID\",\n });\n }\n break;\n }\n }\n}\n\n/**\n * Validates a single field configuration.\n */\nfunction validateField(\n field: unknown,\n path: string,\n errors: FormValidationError[],\n seenNames: Set<string>\n): void {\n if (!field || typeof field !== \"object\") {\n return;\n }\n\n const f = field as Record<string, unknown>;\n\n // Validate field type\n const fieldType = f.type;\n if (!fieldType) {\n errors.push({\n path: `${path}.type`,\n message: \"Field type is required\",\n code: \"FIELD_TYPE_REQUIRED\",\n });\n return;\n }\n\n if (typeof fieldType !== \"string\" || !VALID_FIELD_TYPES_SET.has(fieldType)) {\n let fieldTypeRepr: string;\n if (typeof fieldType === \"object\" && fieldType !== null) {\n fieldTypeRepr = JSON.stringify(fieldType);\n } else {\n // eslint-disable-next-line @typescript-eslint/no-base-to-string -- fieldType narrowed to primitive above; rule doesn't follow control flow on unknown\n fieldTypeRepr = String(fieldType);\n }\n errors.push({\n path: `${path}.type`,\n message: `Invalid field type '${fieldTypeRepr}'. Valid types: ${VALID_FIELD_TYPES.join(\", \")}`,\n code: \"FIELD_TYPE_INVALID\",\n });\n return;\n }\n\n validateFieldName(f.name, path, errors, seenNames);\n\n // All fields except hidden require a label\n if (fieldType !== \"hidden\") {\n validateFieldLabel(f.label, path, errors);\n }\n\n // Type-specific validation\n validateFieldByType(field as FormField, path, errors);\n\n // Validate conditional logic if present\n if (f.conditionalLogic) {\n validateConditionalLogic(field as FormField, path, errors);\n }\n}\n\n/**\n * Validates an array of field configurations.\n */\nfunction validateFieldsArray(\n fields: unknown[],\n basePath: string,\n errors: FormValidationError[]\n): void {\n // Track field names for duplicate detection\n const seenNames = new Set<string>();\n\n fields.forEach((field, index) => {\n const fieldPath = `${basePath}[${index}]`;\n validateField(field, fieldPath, errors, seenNames);\n });\n}\n\n/**\n * Validates the top-level fields array.\n */\nfunction validateFields(fields: unknown, errors: FormValidationError[]): void {\n const path = \"fields\";\n\n if (!fields) {\n errors.push({\n path,\n message: \"Form must have at least one field\",\n code: \"FIELDS_REQUIRED\",\n });\n return;\n }\n\n if (!Array.isArray(fields)) {\n errors.push({\n path,\n message: \"Form fields must be an array\",\n code: \"FIELDS_INVALID_TYPE\",\n });\n return;\n }\n\n if (fields.length === 0) {\n errors.push({\n path,\n message: \"Form must have at least one field\",\n code: \"FIELDS_EMPTY\",\n });\n return;\n }\n\n validateFieldsArray(fields, path, errors);\n}\n\n/**\n * Validates form settings.\n */\nfunction validateSettings(\n settings: unknown,\n errors: FormValidationError[]\n): void {\n if (!settings) {\n return; // Settings are optional\n }\n\n if (typeof settings !== \"object\" || Array.isArray(settings)) {\n errors.push({\n path: \"settings\",\n message: \"Form settings must be an object\",\n code: \"SETTINGS_INVALID_TYPE\",\n });\n return;\n }\n\n const s = settings as Record<string, unknown>;\n\n // If confirmationType is \"redirect\", redirectUrl or redirectRelation is required\n if (s.confirmationType === \"redirect\") {\n if (!s.redirectUrl && !s.redirectRelation) {\n errors.push({\n path: \"settings.redirectUrl\",\n message:\n \"Redirect URL or redirect relation is required when confirmation type is 'redirect'\",\n code: \"REDIRECT_URL_REQUIRED\",\n });\n }\n }\n}\n\n/**\n * Validates form access control.\n */\nfunction validateAccess(access: unknown, errors: FormValidationError[]): void {\n if (!access) {\n return; // Access is optional\n }\n\n if (typeof access !== \"object\" || Array.isArray(access)) {\n errors.push({\n path: \"access\",\n message: \"Access control must be an object\",\n code: \"ACCESS_INVALID_TYPE\",\n });\n return;\n }\n\n const accessObj = access as Record<string, unknown>;\n const validAccessKeys = [\"submit\", \"read\"];\n\n for (const key of validAccessKeys) {\n const fn = accessObj[key];\n if (fn !== undefined && typeof fn !== \"function\") {\n errors.push({\n path: `access.${key}`,\n message: `Access control '${key}' must be a function`,\n code: \"ACCESS_FUNCTION_INVALID\",\n });\n }\n }\n}\n\n// ============================================================\n// Main Validation Function\n// ============================================================\n\n/**\n * Validates a complete form configuration.\n *\n * This is the main entry point for form validation. It performs\n * comprehensive validation including:\n * - Slug format and reserved name checking\n * - Field name validation\n * - Field-specific validation (options, min/max, etc.)\n * - Duplicate field name detection\n * - Conditional logic validation\n * - Settings validation\n * - Access function type validation\n *\n * @param config - The form configuration to validate\n * @returns Validation result with any errors found\n *\n * @example\n * ```typescript\n * import { validateFormConfig } from '@nextly/plugin-form-builder';\n *\n * const config = {\n * slug: 'contact-form',\n * fields: [\n * { type: 'text', name: 'firstName', label: 'First Name' },\n * { type: 'email', name: 'email', label: 'Email', required: true },\n * ],\n * };\n *\n * const result = validateFormConfig(config);\n *\n * if (!result.valid) {\n * result.errors.forEach(err => {\n * console.error(`[${err.code}] ${err.path}: ${err.message}`);\n * });\n * }\n * ```\n */\nexport function validateFormConfig(config: FormConfig): FormValidationResult {\n const errors: FormValidationError[] = [];\n\n // Validate slug\n validateSlug(config.slug, errors);\n\n // Validate fields\n validateFields(config.fields, errors);\n\n // Validate settings\n validateSettings(config.settings, errors);\n\n // Validate access control\n validateAccess(config.access, errors);\n\n return {\n valid: errors.length === 0,\n errors,\n };\n}\n\n/**\n * Throws an error if the form configuration is invalid.\n *\n * This is a convenience wrapper around `validateFormConfig` that\n * throws a descriptive error instead of returning a result object.\n *\n * @param config - The form configuration to validate\n * @throws Error if validation fails with all error messages\n *\n * @example\n * ```typescript\n * import { assertValidFormConfig } from '@nextly/plugin-form-builder';\n *\n * // Throws if invalid\n * assertValidFormConfig(config);\n * ```\n */\nexport function assertValidFormConfig(config: FormConfig): void {\n const result = validateFormConfig(config);\n\n if (!result.valid) {\n const errorMessages = result.errors\n .map(err => ` - [${err.code}] ${err.path}: ${err.message}`)\n .join(\"\\n\");\n\n throw new Error(\n `Invalid form config for '${config.slug || \"unknown\"}':\\n${errorMessages}`\n );\n }\n}\n","/**\n * Form Configuration Defaults\n *\n * Applies default values to form configurations, ensuring all required\n * properties have sensible defaults while preserving user-specified values.\n *\n * @module config/defaults\n * @since 1.0.0\n *\n * @example\n * ```typescript\n * import { applyFormDefaults } from '@nextly/plugin-form-builder';\n *\n * const config = {\n * slug: 'contact-form',\n * fields: [\n * { type: 'text', name: 'name', label: 'Name' },\n * { type: 'email', name: 'email', label: 'Email' },\n * ],\n * };\n *\n * const configWithDefaults = applyFormDefaults(config);\n * // configWithDefaults now has labels, settings, etc. with defaults applied\n * ```\n */\n\nimport type { FormConfig, FormSettings, FormNotifications } from \"../types\";\n\n// ============================================================\n// Default Values\n// ============================================================\n\n/**\n * Default form settings.\n */\nexport const DEFAULT_FORM_SETTINGS: Required<\n Pick<\n FormSettings,\n | \"submitButtonText\"\n | \"confirmationType\"\n | \"successMessage\"\n | \"allowMultipleSubmissions\"\n >\n> = {\n submitButtonText: \"Submit\",\n confirmationType: \"message\",\n successMessage: \"Thank you for your submission!\",\n allowMultipleSubmissions: true,\n};\n\n/**\n * Default notification settings.\n */\nexport const DEFAULT_NOTIFICATION_SETTINGS: Pick<\n FormNotifications,\n \"enabled\" | \"replyTo\"\n> = {\n enabled: true,\n replyTo: true,\n};\n\n// ============================================================\n// Helper Functions\n// ============================================================\n\n/**\n * Converts a slug to title case.\n *\n * @param str - The slug string to convert\n * @returns Title case string\n *\n * @example\n * ```typescript\n * toTitleCase('contact-form'); // 'Contact Form'\n * toTitleCase('newsletter_signup'); // 'Newsletter Signup'\n * ```\n */\nexport function toTitleCase(str: string): string {\n return str.replace(/[-_]/g, \" \").replace(/\\b\\w/g, char => char.toUpperCase());\n}\n\n/**\n * Generates a plural form from a singular label.\n * Handles common English pluralization rules.\n *\n * @param singular - The singular form\n * @returns The plural form\n *\n * @example\n * ```typescript\n * pluralize('Form'); // 'Forms'\n * pluralize('Category'); // 'Categories'\n * pluralize('Quiz'); // 'Quizzes'\n * ```\n */\nexport function pluralize(singular: string): string {\n if (singular.endsWith(\"y\") && !/[aeiou]y$/i.test(singular)) {\n return singular.slice(0, -1) + \"ies\";\n }\n if (\n singular.endsWith(\"s\") ||\n singular.endsWith(\"x\") ||\n singular.endsWith(\"z\") ||\n singular.endsWith(\"ch\") ||\n singular.endsWith(\"sh\")\n ) {\n return singular + \"es\";\n }\n return singular + \"s\";\n}\n\n// ============================================================\n// Main Default Application Function\n// ============================================================\n\n/**\n * Apply default values to form configuration.\n *\n * This function takes a partial form configuration and returns a complete\n * configuration with all default values applied. User-specified values\n * are preserved and take precedence over defaults.\n *\n * @param config - Form configuration (may be partial)\n * @returns Configuration with defaults applied\n *\n * @example\n * ```typescript\n * import { applyFormDefaults } from '@nextly/plugin-form-builder';\n *\n * const config = {\n * slug: 'contact-form',\n * fields: [\n * { type: 'text', name: 'name', label: 'Name' },\n * { type: 'email', name: 'email', label: 'Email' },\n * ],\n * };\n *\n * const result = applyFormDefaults(config);\n *\n * // Result:\n * // {\n * // slug: 'contact-form',\n * // labels: { singular: 'Contact Form', plural: 'Contact Forms' },\n * // fields: [...],\n * // settings: {\n * // submitButtonText: 'Submit',\n * // confirmationType: 'message',\n * // successMessage: 'Thank you for your submission!',\n * // allowMultipleSubmissions: true,\n * // },\n * // access: {\n * // submit: () => true,\n * // read: ({ req }) => !!req.user,\n * // },\n * // }\n * ```\n */\nexport function applyFormDefaults(config: FormConfig): FormConfig {\n // Generate singular label from slug if not provided\n const singularLabel = config.labels?.singular || toTitleCase(config.slug);\n\n return {\n ...config,\n\n // Generate labels from slug if not provided\n labels: {\n singular: singularLabel,\n plural: config.labels?.plural || pluralize(singularLabel),\n },\n\n // Apply settings defaults\n settings: {\n submitButtonText:\n config.settings?.submitButtonText ||\n DEFAULT_FORM_SETTINGS.submitButtonText,\n confirmationType:\n config.settings?.confirmationType ||\n DEFAULT_FORM_SETTINGS.confirmationType,\n successMessage:\n config.settings?.successMessage || DEFAULT_FORM_SETTINGS.successMessage,\n allowMultipleSubmissions:\n config.settings?.allowMultipleSubmissions ??\n DEFAULT_FORM_SETTINGS.allowMultipleSubmissions,\n // Preserve other settings that don't have defaults\n redirectUrl: config.settings?.redirectUrl,\n redirectRelation: config.settings?.redirectRelation,\n captcha: config.settings?.captcha,\n },\n\n // Apply notification defaults if notifications are configured\n notifications: config.notifications\n ? {\n enabled:\n config.notifications.enabled ??\n DEFAULT_NOTIFICATION_SETTINGS.enabled,\n recipients: config.notifications.recipients || [],\n subject:\n config.notifications.subject ||\n `New submission for ${singularLabel}`,\n replyTo:\n config.notifications.replyTo ??\n DEFAULT_NOTIFICATION_SETTINGS.replyTo,\n template: config.notifications.template,\n }\n : undefined,\n\n // Apply access defaults\n access: {\n // Default: anyone can submit the form\n submit: config.access?.submit || (() => true),\n // Default: only authenticated users can view submissions\n read: config.access?.read || (({ req }) => !!req.user),\n },\n };\n}\n\n/**\n * Apply default values to individual form field.\n *\n * This is a utility function for applying defaults to a single field,\n * useful when processing fields individually (e.g., in the Schema Builder).\n *\n * @param field - The field to apply defaults to\n * @returns Field with defaults applied\n *\n * @example\n * ```typescript\n * import { applyFieldDefaults } from '@nextly/plugin-form-builder';\n *\n * const field = { type: 'text', name: 'firstName' };\n * const fieldWithDefaults = applyFieldDefaults(field);\n * // { type: 'text', name: 'firstName', label: 'First Name', required: false }\n * ```\n */\nexport function applyFieldDefaults<\n T extends { type: string; name?: string; label?: string; required?: boolean },\n>(field: T): T {\n // Generate label from name if not provided\n const label =\n field.label ||\n (field.name\n ? toTitleCase(field.name.replace(/([A-Z])/g, \" $1\").trim())\n : \"\");\n\n return {\n ...field,\n label,\n required: field.required ?? false,\n };\n}\n\n/**\n * Creates a complete form config with minimal input.\n *\n * This is a convenience function for quickly creating form configs\n * with sensible defaults. Only the slug and fields are required.\n *\n * @param slug - The form slug\n * @param fields - Array of form fields\n * @param options - Optional additional configuration\n * @returns Complete form configuration\n *\n * @example\n * ```typescript\n * import { createFormConfig } from '@nextly/plugin-form-builder';\n *\n * const config = createFormConfig('contact', [\n * { type: 'text', name: 'name', label: 'Name', required: true },\n * { type: 'email', name: 'email', label: 'Email', required: true },\n * { type: 'textarea', name: 'message', label: 'Message' },\n * ]);\n * ```\n */\nexport function createFormConfig(\n slug: string,\n fields: FormConfig[\"fields\"],\n options?: Partial<Omit<FormConfig, \"slug\" | \"fields\">>\n): FormConfig {\n return applyFormDefaults({\n slug,\n fields,\n ...options,\n });\n}\n","/**\n * Form Field Helpers\n *\n * Convenient factory functions for creating form field configurations.\n * These helpers eliminate the need to manually specify the `type` property,\n * providing a cleaner, more ergonomic API for form definitions.\n *\n * @module config/field-helpers\n *\n * @example\n * ```typescript\n * import {\n * text,\n * email,\n * select,\n * checkbox,\n * option,\n * } from '@nextly/plugin-form-builder';\n *\n * const contactFormFields = [\n * text({ name: 'firstName', label: 'First Name', required: true }),\n * text({ name: 'lastName', label: 'Last Name', required: true }),\n * email({ name: 'email', label: 'Email Address', required: true }),\n * select({\n * name: 'subject',\n * label: 'Subject',\n * options: [\n * option('General Inquiry'),\n * option('Support Request'),\n * option('Feedback'),\n * ],\n * }),\n * checkbox({ name: 'subscribe', label: 'Subscribe to newsletter' }),\n * ];\n * ```\n */\n\nimport type {\n TextFormField,\n EmailFormField,\n NumberFormField,\n PhoneFormField,\n UrlFormField,\n TextareaFormField,\n SelectFormField,\n CheckboxFormField,\n RadioFormField,\n FileFormField,\n DateFormField,\n TimeFormField,\n HiddenFormField,\n} from \"../types\";\n\n// ============================================================\n// Utility Types\n// ============================================================\n\n/**\n * Option for select and radio fields.\n */\nexport interface FormFieldOption {\n label: string;\n value: string;\n}\n\n// ============================================================\n// Utility Helpers\n// ============================================================\n\n/**\n * Creates an option for select or radio fields.\n *\n * If no value is provided, the label is converted to lowercase with\n * spaces replaced by underscores.\n *\n * @param label - Display label for the option\n * @param value - Optional value (defaults to lowercase label with underscores)\n * @returns Option object with label and value\n *\n * @example\n * ```typescript\n * // Value derived from label\n * option('United States') // { label: 'United States', value: 'united_states' }\n *\n * // Explicit value\n * option('United States', 'us') // { label: 'United States', value: 'us' }\n *\n * // Usage in select field\n * select({\n * name: 'country',\n * label: 'Country',\n * options: [\n * option('United States', 'us'),\n * option('Canada', 'ca'),\n * option('Mexico', 'mx'),\n * ],\n * })\n * ```\n */\nexport const option = (label: string, value?: string): FormFieldOption => ({\n label,\n value: value ?? label.toLowerCase().replace(/\\s+/g, \"_\"),\n});\n\n// ============================================================\n// Text Input Field Helpers\n// ============================================================\n\n/**\n * Creates a text input field configuration.\n *\n * Text fields store simple string values with optional constraints\n * like min/max length and pattern validation.\n *\n * @param config - Field configuration without the `type` property\n * @returns Complete text field configuration\n *\n * @example\n * ```typescript\n * // Basic text field\n * text({ name: 'firstName', label: 'First Name', required: true })\n *\n * // Text with validation\n * text({\n * name: 'username',\n * label: 'Username',\n * required: true,\n * placeholder: 'Enter username',\n * validation: {\n * minLength: 3,\n * maxLength: 20,\n * pattern: '^[a-zA-Z0-9_]+$',\n * },\n * })\n * ```\n */\nexport const text = (config: Omit<TextFormField, \"type\">): TextFormField => ({\n ...config,\n type: \"text\",\n});\n\n/**\n * Creates an email input field configuration.\n *\n * Email fields provide built-in email format validation.\n *\n * @param config - Field configuration without the `type` property\n * @returns Complete email field configuration\n *\n * @example\n * ```typescript\n * // Basic email field\n * email({ name: 'email', label: 'Email Address', required: true })\n *\n * // Email with placeholder\n * email({\n * name: 'workEmail',\n * label: 'Work Email',\n * required: true,\n * placeholder: 'you@company.com',\n * })\n * ```\n */\nexport const email = (\n config: Omit<EmailFormField, \"type\">\n): EmailFormField => ({\n ...config,\n type: \"email\",\n});\n\n/**\n * Creates a phone input field configuration.\n *\n * Phone fields are optimized for phone number entry with\n * appropriate keyboard on mobile devices.\n *\n * @param config - Field configuration without the `type` property\n * @returns Complete phone field configuration\n *\n * @example\n * ```typescript\n * // Basic phone field\n * phone({ name: 'phone', label: 'Phone Number', required: true })\n *\n * // Phone with placeholder\n * phone({\n * name: 'mobile',\n * label: 'Mobile Number',\n * placeholder: '+1 (555) 123-4567',\n * })\n * ```\n */\nexport const phone = (\n config: Omit<PhoneFormField, \"type\">\n): PhoneFormField => ({\n ...config,\n type: \"phone\",\n});\n\n/**\n * Creates a URL input field configuration.\n *\n * URL fields provide built-in URL format validation.\n *\n * @param config - Field configuration without the `type` property\n * @returns Complete URL field configuration\n *\n * @example\n * ```typescript\n * // Basic URL field\n * url({ name: 'website', label: 'Website URL' })\n *\n * // URL with placeholder\n * url({\n * name: 'portfolio',\n * label: 'Portfolio URL',\n * placeholder: 'https://example.com',\n * })\n * ```\n */\nexport const url = (config: Omit<UrlFormField, \"type\">): UrlFormField => ({\n ...config,\n type: \"url\",\n});\n\n/**\n * Creates a textarea field configuration.\n *\n * Textarea fields provide multi-line text input with configurable\n * row count and length validation.\n *\n * @param config - Field configuration without the `type` property\n * @returns Complete textarea field configuration\n *\n * @example\n * ```typescript\n * // Basic textarea\n * textarea({ name: 'message', label: 'Message', required: true })\n *\n * // Textarea with rows and validation\n * textarea({\n * name: 'description',\n * label: 'Description',\n * rows: 5,\n * validation: {\n * minLength: 10,\n * maxLength: 500,\n * },\n * })\n * ```\n */\nexport const textarea = (\n config: Omit<TextareaFormField, \"type\">\n): TextareaFormField => ({\n ...config,\n type: \"textarea\",\n});\n\n// ============================================================\n// Numeric Field Helpers\n// ============================================================\n\n/**\n * Creates a number input field configuration.\n *\n * Number fields store numeric values with optional min/max/step\n * constraints.\n *\n * @param config - Field configuration without the `type` property\n * @returns Complete number field configuration\n *\n * @example\n * ```typescript\n * // Basic number field\n * number({ name: 'age', label: 'Age', required: true })\n *\n * // Number with range validation\n * number({\n * name: 'quantity',\n * label: 'Quantity',\n * required: true,\n * validation: {\n * min: 1,\n * max: 100,\n * step: 1,\n * },\n * })\n * ```\n */\nexport const number = (\n config: Omit<NumberFormField, \"type\">\n): NumberFormField => ({\n ...config,\n type: \"number\",\n});\n\n// ============================================================\n// Selection Field Helpers\n// ============================================================\n\n/**\n * Creates a select dropdown field configuration.\n *\n * Select fields display a dropdown list of options. Use the `option()`\n * helper to create options.\n *\n * @param config - Field configuration without the `type` property\n * @returns Complete select field configuration\n *\n * @example\n * ```typescript\n * // Basic select\n * select({\n * name: 'country',\n * label: 'Country',\n * required: true,\n * options: [\n * option('United States', 'us'),\n * option('Canada', 'ca'),\n * option('Mexico', 'mx'),\n * ],\n * })\n *\n * // Multi-select\n * select({\n * name: 'interests',\n * label: 'Interests',\n * allowMultiple: true,\n * options: [\n * option('Technology'),\n * option('Design'),\n * option('Business'),\n * ],\n * })\n * ```\n */\nexport const select = (\n config: Omit<SelectFormField, \"type\">\n): SelectFormField => ({\n ...config,\n type: \"select\",\n});\n\n/**\n * Creates a single checkbox field configuration.\n *\n * Checkbox fields store boolean values (true/false).\n *\n * @param config - Field configuration without the `type` property\n * @returns Complete checkbox field configuration\n *\n * @example\n * ```typescript\n * // Terms agreement checkbox\n * checkbox({\n * name: 'agreeToTerms',\n * label: 'I agree to the terms and conditions',\n * required: true,\n * })\n *\n * // Newsletter subscription\n * checkbox({\n * name: 'subscribe',\n * label: 'Subscribe to our newsletter',\n * defaultValue: true,\n * })\n * ```\n */\nexport const checkbox = (\n config: Omit<CheckboxFormField, \"type\">\n): CheckboxFormField => ({\n ...config,\n type: \"checkbox\",\n});\n\n/**\n * Creates a radio group field configuration.\n *\n * Radio group fields allow selecting exactly one option from a list.\n * Use the `option()` helper to create options.\n *\n * @param config - Field configuration without the `type` property\n * @returns Complete radio field configuration\n *\n * @example\n * ```typescript\n * radio({\n * name: 'contactMethod',\n * label: 'Preferred Contact Method',\n * required: true,\n * options: [\n * option('Email', 'email'),\n * option('Phone', 'phone'),\n * option('SMS', 'sms'),\n * ],\n * })\n * ```\n */\nexport const radio = (\n config: Omit<RadioFormField, \"type\">\n): RadioFormField => ({\n ...config,\n type: \"radio\",\n});\n\n// ============================================================\n// File Field Helpers\n// ============================================================\n\n/**\n * Creates a file upload field configuration.\n *\n * File fields allow users to upload files with configurable\n * accepted types, size limits, and multiple file support.\n *\n * @param config - Field configuration without the `type` property\n * @returns Complete file field configuration\n *\n * @example\n * ```typescript\n * // Single file upload\n * file({\n * name: 'resume',\n * label: 'Upload Resume',\n * required: true,\n * accept: 'application/pdf,.doc,.docx',\n * maxFileSize: 5242880, // 5MB\n * })\n *\n * // Multiple images\n * file({\n * name: 'photos',\n * label: 'Upload Photos',\n * accept: 'image/*',\n * multiple: true,\n * maxFileSize: 10485760, // 10MB\n * })\n * ```\n */\nexport const file = (config: Omit<FileFormField, \"type\">): FileFormField => ({\n ...config,\n type: \"file\",\n});\n\n// ============================================================\n// Date/Time Field Helpers\n// ============================================================\n\n/**\n * Creates a date picker field configuration.\n *\n * Date fields provide a date picker with optional min/max constraints.\n *\n * @param config - Field configuration without the `type` property\n * @returns Complete date field configuration\n *\n * @example\n * ```typescript\n * // Basic date field\n * date({ name: 'birthdate', label: 'Date of Birth', required: true })\n *\n * // Date with range constraints\n * date({\n * name: 'eventDate',\n * label: 'Event Date',\n * required: true,\n * min: '2024-01-01',\n * max: '2024-12-31',\n * })\n * ```\n */\nexport const date = (config: Omit<DateFormField, \"type\">): DateFormField => ({\n ...config,\n type: \"date\",\n});\n\n/**\n * Creates a time picker field configuration.\n *\n * Time fields provide a time picker for selecting time values.\n *\n * @param config - Field configuration without the `type` property\n * @returns Complete time field configuration\n *\n * @example\n * ```typescript\n * // Basic time field\n * time({ name: 'appointmentTime', label: 'Preferred Time', required: true })\n *\n * // Time with default\n * time({\n * name: 'meetingTime',\n * label: 'Meeting Time',\n * defaultValue: '09:00',\n * })\n * ```\n */\nexport const time = (config: Omit<TimeFormField, \"type\">): TimeFormField => ({\n ...config,\n type: \"time\",\n});\n\n// ============================================================\n// Special Field Helpers\n// ============================================================\n\n/**\n * Creates a hidden field configuration.\n *\n * Hidden fields store values that are not visible to users but\n * are included in the submission data. Useful for tracking\n * metadata like source, campaign, or referrer.\n *\n * @param config - Field configuration without the `type` property\n * @returns Complete hidden field configuration\n *\n * @example\n * ```typescript\n * // Track form source\n * hidden({ name: 'source', label: 'Source', defaultValue: 'website' })\n *\n * // Campaign tracking\n * hidden({ name: 'campaign', label: 'Campaign', defaultValue: 'summer-sale' })\n * ```\n */\nexport const hidden = (\n config: Omit<HiddenFormField, \"type\">\n): HiddenFormField => ({\n ...config,\n type: \"hidden\",\n});\n","/**\n * Conditional Logic Evaluator\n *\n * Evaluates conditional logic rules to determine field visibility in forms.\n * Used by the FormRenderer component to show/hide fields based on user input.\n *\n * @module utils/evaluate-conditions\n * @since 1.0.0\n *\n * @example\n * ```typescript\n * import { evaluateConditions } from '@nextly/plugin-form-builder';\n *\n * const conditionalLogic = {\n * enabled: true,\n * action: 'show',\n * operator: 'AND',\n * conditions: [\n * { field: 'country', comparison: 'equals', value: 'US' },\n * { field: 'age', comparison: 'greaterThan', value: 18 },\n * ],\n * };\n *\n * const formData = { country: 'US', age: 25 };\n * const isVisible = evaluateConditions(conditionalLogic, formData);\n * // isVisible = true (all conditions met, action is 'show')\n * ```\n */\n\nimport type { ConditionalLogic, ConditionalLogicCondition } from \"../types\";\n\n// ============================================================\n// Types\n// ============================================================\n\n/**\n * Comparison operators supported by the conditional logic system.\n */\nexport type ComparisonOperator =\n | \"equals\"\n | \"notEquals\"\n | \"contains\"\n | \"isEmpty\"\n | \"isNotEmpty\"\n | \"greaterThan\"\n | \"lessThan\";\n\n// ============================================================\n// Internal Helper Functions\n// ============================================================\n\n/**\n * Checks if a value is considered \"empty\".\n * Empty values: undefined, null, empty string, empty array.\n */\nfunction isEmptyValue(value: unknown): boolean {\n if (value === undefined || value === null) {\n return true;\n }\n if (typeof value === \"string\" && value.trim() === \"\") {\n return true;\n }\n if (Array.isArray(value) && value.length === 0) {\n return true;\n }\n return false;\n}\n\n/**\n * Converts a value to a number for numeric comparisons.\n * Returns NaN if conversion is not possible.\n */\nfunction toNumber(value: unknown): number {\n if (typeof value === \"number\") {\n return value;\n }\n if (typeof value === \"string\") {\n const trimmed = value.trim();\n if (trimmed === \"\") {\n return NaN;\n }\n return Number(trimmed);\n }\n return NaN;\n}\n\n/**\n * Converts a value to a string for string comparisons.\n */\nfunction toString(value: unknown): string {\n if (value === undefined || value === null) {\n return \"\";\n }\n if (typeof value === \"string\") {\n return value;\n }\n if (Array.isArray(value)) {\n return value.join(\", \");\n }\n if (typeof value === \"object\") {\n return JSON.stringify(value);\n }\n // Primitive (number, boolean, bigint, symbol) — safe to coerce.\n // eslint-disable-next-line @typescript-eslint/no-base-to-string -- value narrowed to primitive above; rule doesn't follow control flow on unknown\n return String(value);\n}\n\n/**\n * Compares two values using the specified operator.\n *\n * @param operator - The comparison operator to use\n * @param fieldValue - The actual value from the form data\n * @param conditionValue - The expected value from the condition\n * @returns Whether the comparison is true\n */\nfunction compareValues(\n operator: ComparisonOperator,\n fieldValue: unknown,\n conditionValue: unknown\n): boolean {\n switch (operator) {\n case \"equals\": {\n // Strict equality for most cases\n // Handle array values (e.g., checkbox groups, multi-selects)\n if (Array.isArray(fieldValue)) {\n // If conditionValue is also an array, check for deep equality\n if (Array.isArray(conditionValue)) {\n if (fieldValue.length !== conditionValue.length) {\n return false;\n }\n return fieldValue.every((v, i) => v === conditionValue[i]);\n }\n // Check if the array contains the condition value\n return fieldValue.includes(conditionValue);\n }\n // Standard equality - string comparison for flexibility\n return toString(fieldValue) === toString(conditionValue);\n }\n\n case \"notEquals\": {\n // Inverse of equals\n if (Array.isArray(fieldValue)) {\n if (Array.isArray(conditionValue)) {\n if (fieldValue.length !== conditionValue.length) {\n return true;\n }\n return !fieldValue.every((v, i) => v === conditionValue[i]);\n }\n return !fieldValue.includes(conditionValue);\n }\n return toString(fieldValue) !== toString(conditionValue);\n }\n\n case \"contains\": {\n // Case-insensitive substring check\n const fieldStr = toString(fieldValue).toLowerCase();\n const conditionStr = toString(conditionValue).toLowerCase();\n\n // Handle array values - check if any element contains the substring\n if (Array.isArray(fieldValue)) {\n return fieldValue.some(v =>\n toString(v).toLowerCase().includes(conditionStr)\n );\n }\n\n return fieldStr.includes(conditionStr);\n }\n\n case \"isEmpty\": {\n // Check if the field value is empty\n // conditionValue is ignored for this operator\n return isEmptyValue(fieldValue);\n }\n\n case \"isNotEmpty\": {\n // Check if the field value is not empty\n // conditionValue is ignored for this operator\n return !isEmptyValue(fieldValue);\n }\n\n case \"greaterThan\": {\n // Numeric comparison with loose coercion\n const fieldNum = toNumber(fieldValue);\n const conditionNum = toNumber(conditionValue);\n\n // If both can be converted to numbers, use numeric comparison\n if (!Number.isNaN(fieldNum) && !Number.isNaN(conditionNum)) {\n return fieldNum > conditionNum;\n }\n\n // Fall back to string comparison for non-numeric values\n return toString(fieldValue) > toString(conditionValue);\n }\n\n case \"lessThan\": {\n // Numeric comparison with loose coercion\n const fieldNum = toNumber(fieldValue);\n const conditionNum = toNumber(conditionValue);\n\n // If both can be converted to numbers, use numeric comparison\n if (!Number.isNaN(fieldNum) && !Number.isNaN(conditionNum)) {\n return fieldNum < conditionNum;\n }\n\n // Fall back to string comparison for non-numeric values\n return toString(fieldValue) < toString(conditionValue);\n }\n\n default: {\n // Unknown operator - return false to be safe\n // `operator` is `never` here because the switch is exhaustive over ComparisonOperator;\n // cast to string so the diagnostic survives if a new operator is added without a case.\n console.warn(\n `[evaluateConditions] Unknown comparison operator: ${String(operator)}`\n );\n return false;\n }\n }\n}\n\n/**\n * Evaluates a single condition against the form data.\n *\n * @param condition - The condition to evaluate\n * @param formData - The current form data\n * @returns Whether the condition is met\n */\nfunction evaluateSingleCondition(\n condition: ConditionalLogicCondition,\n formData: Record<string, unknown>\n): boolean {\n const { field, comparison, value: conditionValue } = condition;\n\n // Get the field value from form data\n // Support nested field access with dot notation (e.g., \"address.city\")\n const fieldValue = getNestedValue(formData, field);\n\n // Evaluate the comparison\n return compareValues(comparison, fieldValue, conditionValue);\n}\n\n/**\n * Gets a nested value from an object using dot notation.\n *\n * @param obj - The object to get the value from\n * @param path - The path to the value (e.g., \"address.city\")\n * @returns The value at the path, or undefined if not found\n */\nfunction getNestedValue(obj: Record<string, unknown>, path: string): unknown {\n if (!path) {\n return undefined;\n }\n\n // Simple case - no nesting\n if (!path.includes(\".\")) {\n return obj[path];\n }\n\n // Nested case - traverse the path\n const parts = path.split(\".\");\n let current: unknown = obj;\n\n for (const part of parts) {\n if (current === undefined || current === null) {\n return undefined;\n }\n if (typeof current !== \"object\") {\n return undefined;\n }\n current = (current as Record<string, unknown>)[part];\n }\n\n return current;\n}\n\n// ============================================================\n// Main Evaluation Function\n// ============================================================\n\n/**\n * Evaluates conditional logic to determine field visibility.\n *\n * This is the main entry point for conditional logic evaluation.\n * It evaluates all conditions according to the logical operator (AND/OR)\n * and returns whether the field should be visible based on the action\n * (show/hide).\n *\n * @param logic - The conditional logic configuration\n * @param formData - The current form data (field values)\n * @returns Whether the field should be visible\n *\n * @example\n * ```typescript\n * // Show field when country is US AND age is over 18\n * const logic: ConditionalLogic = {\n * enabled: true,\n * action: 'show',\n * operator: 'AND',\n * conditions: [\n * { field: 'country', comparison: 'equals', value: 'US' },\n * { field: 'age', comparison: 'greaterThan', value: 18 },\n * ],\n * };\n *\n * evaluateConditions(logic, { country: 'US', age: 25 }); // true\n * evaluateConditions(logic, { country: 'US', age: 15 }); // false\n * evaluateConditions(logic, { country: 'CA', age: 25 }); // false\n *\n * // Hide field when status is \"completed\"\n * const hideLogic: ConditionalLogic = {\n * enabled: true,\n * action: 'hide',\n * operator: 'AND',\n * conditions: [\n * { field: 'status', comparison: 'equals', value: 'completed' },\n * ],\n * };\n *\n * evaluateConditions(hideLogic, { status: 'completed' }); // false (hidden)\n * evaluateConditions(hideLogic, { status: 'pending' }); // true (visible)\n * ```\n *\n * @example\n * ```typescript\n * // Show field when either condition is met (OR)\n * const orLogic: ConditionalLogic = {\n * enabled: true,\n * action: 'show',\n * operator: 'OR',\n * conditions: [\n * { field: 'role', comparison: 'equals', value: 'admin' },\n * { field: 'role', comparison: 'equals', value: 'editor' },\n * ],\n * };\n *\n * evaluateConditions(orLogic, { role: 'admin' }); // true\n * evaluateConditions(orLogic, { role: 'editor' }); // true\n * evaluateConditions(orLogic, { role: 'viewer' }); // false\n * ```\n */\nexport function evaluateConditions(\n logic: ConditionalLogic | undefined,\n formData: Record<string, unknown>\n): boolean {\n // If no logic provided or logic is disabled, field is visible by default\n if (!logic || !logic.enabled) {\n return true;\n }\n\n const { action, operator, conditions } = logic;\n\n // If no conditions, field is visible by default\n if (!conditions || conditions.length === 0) {\n return true;\n }\n\n // Evaluate all conditions\n const results = conditions.map(condition =>\n evaluateSingleCondition(condition, formData)\n );\n\n // Combine results based on logical operator\n let conditionsMet: boolean;\n\n if (operator === \"OR\") {\n // OR: At least one condition must be true\n conditionsMet = results.some(result => result);\n } else {\n // AND (default): All conditions must be true\n conditionsMet = results.every(result => result);\n }\n\n // Apply the action\n // - \"show\": field is visible when conditions are met\n // - \"hide\": field is visible when conditions are NOT met\n if (action === \"hide\") {\n return !conditionsMet;\n }\n\n // Default action is \"show\"\n return conditionsMet;\n}\n\n/**\n * Checks if a comparison operator is valid.\n *\n * @param operator - The operator to check\n * @returns Whether the operator is valid\n */\nexport function isValidComparisonOperator(\n operator: string\n): operator is ComparisonOperator {\n const validOperators: ComparisonOperator[] = [\n \"equals\",\n \"notEquals\",\n \"contains\",\n \"isEmpty\",\n \"isNotEmpty\",\n \"greaterThan\",\n \"lessThan\",\n ];\n return validOperators.includes(operator as ComparisonOperator);\n}\n\n/**\n * Gets the list of supported comparison operators.\n *\n * @returns Array of supported operator names\n */\nexport function getSupportedComparisonOperators(): ComparisonOperator[] {\n return [\n \"equals\",\n \"notEquals\",\n \"contains\",\n \"isEmpty\",\n \"isNotEmpty\",\n \"greaterThan\",\n \"lessThan\",\n ];\n}\n","/**\n * Dynamic Zod Schema Generator\n *\n * Converts FormField[] configuration to Zod validation schemas.\n * Supports all form field types with proper validation rules.\n *\n * @module utils/generate-schema\n * @since 0.1.0\n */\n\nimport { z } from \"zod\";\n\nimport type {\n FormField,\n TextFormField,\n EmailFormField,\n NumberFormField,\n PhoneFormField,\n UrlFormField,\n TextareaFormField,\n SelectFormField,\n CheckboxFormField,\n RadioFormField,\n FileFormField,\n DateFormField,\n TimeFormField,\n HiddenFormField,\n} from \"../types\";\n\n// ============================================================\n// Main Schema Generator\n// ============================================================\n\n/**\n * Generate a Zod validation schema from form field configuration.\n *\n * Creates a dynamic Zod schema based on the form fields array,\n * applying appropriate validation rules for each field type.\n *\n * @param fields - Array of form field configurations\n * @returns Zod object schema for validating submission data\n *\n * @example\n * ```typescript\n * const fields: FormField[] = [\n * { type: 'text', name: 'firstName', label: 'First Name', required: true },\n * { type: 'email', name: 'email', label: 'Email', required: true },\n * { type: 'number', name: 'age', label: 'Age', validation: { min: 18, max: 120 } },\n * ];\n *\n * const schema = generateZodSchema(fields);\n * const result = schema.safeParse({ firstName: 'John', email: 'john@example.com', age: 25 });\n * ```\n */\nexport function generateZodSchema(\n fields: FormField[]\n): z.ZodObject<Record<string, z.ZodTypeAny>> {\n const schemaShape: Record<string, z.ZodTypeAny> = {};\n\n for (const field of fields) {\n // Skip hidden fields without required flag\n if (field.type === \"hidden\" && !field.required) {\n // Still add to schema but make optional\n schemaShape[field.name] = z.string().optional();\n continue;\n }\n\n const fieldSchema = generateFieldSchema(field);\n if (fieldSchema) {\n schemaShape[field.name] = fieldSchema;\n }\n }\n\n return z.object(schemaShape);\n}\n\n// ============================================================\n// Field Schema Generators\n// ============================================================\n\n/**\n * Generate a Zod schema for a single field.\n */\nfunction generateFieldSchema(field: FormField): z.ZodTypeAny | null {\n switch (field.type) {\n case \"text\":\n return generateTextSchema(field);\n case \"email\":\n return generateEmailSchema(field);\n case \"number\":\n return generateNumberSchema(field);\n case \"phone\":\n return generatePhoneSchema(field);\n case \"url\":\n return generateUrlSchema(field);\n case \"textarea\":\n return generateTextareaSchema(field);\n case \"select\":\n return generateSelectSchema(field);\n case \"checkbox\":\n return generateCheckboxSchema(field);\n case \"radio\":\n return generateRadioSchema(field);\n case \"file\":\n return generateFileSchema(field);\n case \"date\":\n return generateDateSchema(field);\n case \"time\":\n return generateTimeSchema(field);\n case \"hidden\":\n return generateHiddenSchema(field);\n default:\n // Unknown field type - accept any value\n return z.unknown();\n }\n}\n\n/**\n * Generate schema for text field.\n */\nfunction generateTextSchema(field: TextFormField): z.ZodTypeAny {\n let schema = z.string();\n\n // Apply validation rules\n if (field.validation?.minLength !== undefined) {\n schema = schema.min(\n field.validation.minLength,\n field.validation.errorMessage ||\n `Minimum ${field.validation.minLength} characters required`\n );\n }\n\n if (field.validation?.maxLength !== undefined) {\n schema = schema.max(\n field.validation.maxLength,\n field.validation.errorMessage ||\n `Maximum ${field.validation.maxLength} characters allowed`\n );\n }\n\n if (field.validation?.pattern) {\n schema = schema.regex(\n new RegExp(field.validation.pattern),\n field.validation.errorMessage || \"Invalid format\"\n );\n }\n\n return applyRequired(schema, field.required, field.validation?.errorMessage);\n}\n\n/**\n * Generate schema for email field.\n */\nfunction generateEmailSchema(field: EmailFormField): z.ZodTypeAny {\n let schema = z\n .string()\n .email(field.validation?.errorMessage || \"Invalid email address\");\n\n if (field.validation?.pattern) {\n schema = schema.regex(\n new RegExp(field.validation.pattern),\n field.validation.errorMessage || \"Invalid email format\"\n );\n }\n\n return applyRequired(schema, field.required, field.validation?.errorMessage);\n}\n\n/**\n * Generate schema for number field.\n */\nfunction generateNumberSchema(field: NumberFormField): z.ZodTypeAny {\n let schema = z.number({\n required_error: field.validation?.errorMessage || \"This field is required\",\n invalid_type_error: \"Must be a number\",\n });\n\n if (field.validation?.min !== undefined) {\n schema = schema.min(\n field.validation.min,\n field.validation.errorMessage ||\n `Minimum value is ${field.validation.min}`\n );\n }\n\n if (field.validation?.max !== undefined) {\n schema = schema.max(\n field.validation.max,\n field.validation.errorMessage ||\n `Maximum value is ${field.validation.max}`\n );\n }\n\n if (field.required) {\n return schema;\n }\n\n return schema.optional().nullable();\n}\n\n/**\n * Generate schema for phone field.\n */\nfunction generatePhoneSchema(field: PhoneFormField): z.ZodTypeAny {\n let schema = z.string();\n\n if (field.validation?.pattern) {\n schema = schema.regex(\n new RegExp(field.validation.pattern),\n field.validation.errorMessage || \"Invalid phone number format\"\n );\n } else {\n // Default phone pattern - allows common formats\n schema = schema.regex(\n /^[\\d\\s\\-+()]+$/,\n field.validation?.errorMessage || \"Invalid phone number\"\n );\n }\n\n return applyRequired(schema, field.required, field.validation?.errorMessage);\n}\n\n/**\n * Generate schema for URL field.\n */\nfunction generateUrlSchema(field: UrlFormField): z.ZodTypeAny {\n let schema = z.string().url(field.validation?.errorMessage || \"Invalid URL\");\n\n if (field.validation?.pattern) {\n schema = schema.regex(\n new RegExp(field.validation.pattern),\n field.validation.errorMessage || \"Invalid URL format\"\n );\n }\n\n return applyRequired(schema, field.required, field.validation?.errorMessage);\n}\n\n/**\n * Generate schema for textarea field.\n */\nfunction generateTextareaSchema(field: TextareaFormField): z.ZodTypeAny {\n let schema = z.string();\n\n if (field.validation?.minLength !== undefined) {\n schema = schema.min(\n field.validation.minLength,\n field.validation.errorMessage ||\n `Minimum ${field.validation.minLength} characters required`\n );\n }\n\n if (field.validation?.maxLength !== undefined) {\n schema = schema.max(\n field.validation.maxLength,\n field.validation.errorMessage ||\n `Maximum ${field.validation.maxLength} characters allowed`\n );\n }\n\n return applyRequired(schema, field.required, field.validation?.errorMessage);\n}\n\n/**\n * Generate schema for select field.\n */\nfunction generateSelectSchema(field: SelectFormField): z.ZodTypeAny {\n const validValues = field.options.map(opt => opt.value);\n\n if (field.allowMultiple) {\n // Multi-select: array of valid values\n const schema = z.array(z.enum(validValues as [string, ...string[]]));\n\n if (field.required) {\n return schema.min(\n 1,\n field.validation?.errorMessage || \"Please select at least one option\"\n );\n }\n\n return schema.optional();\n }\n\n // Single select\n const schema = z.enum(validValues as [string, ...string[]], {\n errorMap: () => ({\n message: field.validation?.errorMessage || \"Please select a valid option\",\n }),\n });\n\n if (field.required) {\n return schema;\n }\n\n return schema.optional();\n}\n\n/**\n * Generate schema for checkbox field.\n */\nfunction generateCheckboxSchema(field: CheckboxFormField): z.ZodTypeAny {\n const schema = z.boolean();\n\n if (field.required) {\n // For required checkbox, value must be true\n return schema.refine(val => val === true, {\n message: field.validation?.errorMessage || \"This field is required\",\n });\n }\n\n return schema.optional();\n}\n\n/**\n * Generate schema for radio field.\n */\nfunction generateRadioSchema(field: RadioFormField): z.ZodTypeAny {\n const validValues = field.options.map(opt => opt.value);\n\n const schema = z.enum(validValues as [string, ...string[]], {\n errorMap: () => ({\n message: field.validation?.errorMessage || \"Please select an option\",\n }),\n });\n\n if (field.required) {\n return schema;\n }\n\n return schema.optional();\n}\n\n/**\n * Generate schema for file field.\n *\n * Note: File validation is typically handled server-side.\n * This schema validates file references (IDs or URLs).\n */\nfunction generateFileSchema(field: FileFormField): z.ZodTypeAny {\n if (field.multiple) {\n const schema = z.array(z.string());\n\n if (field.required) {\n return schema.min(\n 1,\n field.validation?.errorMessage || \"Please upload at least one file\"\n );\n }\n\n return schema.optional();\n }\n\n // Single file\n const schema = z.string();\n return applyRequired(schema, field.required, field.validation?.errorMessage);\n}\n\n/**\n * Generate schema for date field.\n */\nfunction generateDateSchema(field: DateFormField): z.ZodTypeAny {\n // Start with base string schema\n let schema: z.ZodTypeAny = z.string();\n\n // Add date format validation\n schema = (schema as z.ZodString).refine(\n val => {\n if (!val) return true; // Allow empty for optional fields\n const date = new Date(val);\n return !isNaN(date.getTime());\n },\n { message: field.validation?.errorMessage || \"Invalid date\" }\n );\n\n // Min/max date validation\n if (field.min || field.max) {\n schema = schema.refine(\n (val: string) => {\n if (!val) return true;\n const date = new Date(val);\n if (field.min && date < new Date(field.min)) return false;\n if (field.max && date > new Date(field.max)) return false;\n return true;\n },\n {\n message:\n field.validation?.errorMessage ||\n `Date must be between ${field.min || \"any\"} and ${field.max || \"any\"}`,\n }\n );\n }\n\n // Handle required vs optional\n if (field.required) {\n // Add non-empty check for required fields\n return schema.refine(\n (val: string) => val !== undefined && val !== null && val !== \"\",\n { message: field.validation?.errorMessage || \"This field is required\" }\n );\n }\n\n return schema.optional().or(z.literal(\"\"));\n}\n\n/**\n * Generate schema for time field.\n */\nfunction generateTimeSchema(field: TimeFormField): z.ZodTypeAny {\n const schema = z\n .string()\n .regex(\n /^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$/,\n field.validation?.errorMessage || \"Invalid time format (HH:mm)\"\n );\n\n return applyRequired(schema, field.required, field.validation?.errorMessage);\n}\n\n/**\n * Generate schema for hidden field.\n */\nfunction generateHiddenSchema(field: HiddenFormField): z.ZodTypeAny {\n const schema = z.string();\n return applyRequired(schema, field.required, field.validation?.errorMessage);\n}\n\n// ============================================================\n// Helper Functions\n// ============================================================\n\n/**\n * Apply required/optional to a schema.\n */\nfunction applyRequired(\n schema: z.ZodString,\n required?: boolean,\n errorMessage?: string\n): z.ZodTypeAny {\n if (required) {\n return schema.min(1, errorMessage || \"This field is required\");\n }\n\n return schema.optional().or(z.literal(\"\"));\n}\n\n// ============================================================\n// Data Transformation\n// ============================================================\n\n/**\n * Transform form data before validation.\n *\n * Handles type coercion and normalization:\n * - Converts string numbers to actual numbers\n * - Converts string booleans to actual booleans\n * - Trims string values\n * - Handles empty strings\n *\n * @param data - Raw form data\n * @param fields - Form field configurations\n * @returns Transformed data ready for validation\n */\nexport function transformFormData(\n data: Record<string, unknown>,\n fields: FormField[]\n): Record<string, unknown> {\n const transformed: Record<string, unknown> = {};\n\n for (const field of fields) {\n const value = data[field.name];\n\n // Skip undefined values\n if (value === undefined) {\n continue;\n }\n\n transformed[field.name] = transformFieldValue(value, field);\n }\n\n return transformed;\n}\n\n/**\n * Transform a single field value based on its type.\n */\nfunction transformFieldValue(value: unknown, field: FormField): unknown {\n // Handle null/undefined\n if (value === null || value === undefined) {\n return value;\n }\n\n switch (field.type) {\n case \"number\":\n // Coerce to number\n if (typeof value === \"string\") {\n const num = parseFloat(value);\n return isNaN(num) ? value : num;\n }\n return value;\n\n case \"checkbox\":\n // Coerce to boolean\n if (typeof value === \"string\") {\n return value === \"true\" || value === \"1\" || value === \"on\";\n }\n return Boolean(value);\n\n case \"text\":\n case \"email\":\n case \"phone\":\n case \"url\":\n case \"textarea\":\n case \"hidden\":\n // Trim strings\n if (typeof value === \"string\") {\n return value.trim();\n }\n return value;\n\n case \"select\":\n // Ensure array for multi-select\n if (field.allowMultiple) {\n if (!Array.isArray(value)) {\n return value ? [value] : [];\n }\n }\n return value;\n\n default:\n return value;\n }\n}\n\n// ============================================================\n// Validation Helper\n// ============================================================\n\n/**\n * Validate form data against a form's field configuration.\n *\n * Combines transformation and validation in one step.\n *\n * @param data - Raw form data\n * @param fields - Form field configurations\n * @returns Zod safe parse result\n *\n * @example\n * ```typescript\n * const result = validateFormData({ name: 'John', email: 'john@example.com' }, fields);\n *\n * if (result.success) {\n * console.log('Valid data:', result.data);\n * } else {\n * console.log('Validation errors:', result.error.flatten());\n * }\n * ```\n */\nexport function validateFormData(\n data: Record<string, unknown>,\n fields: FormField[]\n): z.SafeParseReturnType<Record<string, unknown>, Record<string, unknown>> {\n const transformed = transformFormData(data, fields);\n const schema = generateZodSchema(fields);\n return schema.safeParse(transformed);\n}\n\n/**\n * Get validation errors as a flat object.\n *\n * @param result - Zod safe parse result\n * @returns Object mapping field names to error messages\n */\nexport function getValidationErrors(\n result: z.SafeParseReturnType<unknown, unknown>\n): Record<string, string> {\n if (result.success) {\n return {};\n }\n\n const errors: Record<string, string> = {};\n\n for (const error of result.error.errors) {\n const path = error.path.join(\".\");\n if (!errors[path]) {\n errors[path] = error.message;\n }\n }\n\n return errors;\n}\n","/**\n * Export Formats Utilities\n *\n * Pure utility functions for exporting form submissions to CSV and JSON formats.\n * These functions generate formatted strings that can be used for file downloads\n * or API responses.\n *\n * @module utils/export-formats\n * @since 0.1.0\n */\n\nimport type { SubmissionDocument, FormDocument, FormField } from \"../types\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Options for CSV export.\n */\nexport interface CSVExportOptions {\n /**\n * Whether to include metadata columns (_id, _status, _submittedAt, etc.).\n * @default true\n */\n includeMetadata?: boolean;\n\n /**\n * Delimiter character for CSV.\n * @default \",\"\n */\n delimiter?: string;\n\n /**\n * Whether to include UTF-8 BOM for Excel compatibility.\n * @default true\n */\n includeBOM?: boolean;\n\n /**\n * Date format for timestamps.\n * @default \"iso\" (ISO 8601)\n */\n dateFormat?: \"iso\" | \"locale\";\n}\n\n/**\n * Options for JSON export.\n */\nexport interface JSONExportOptions {\n /**\n * Whether to include metadata in each submission.\n * @default true\n */\n includeMetadata?: boolean;\n\n /**\n * Whether to include form definition in the export.\n * @default true\n */\n includeFormDefinition?: boolean;\n\n /**\n * Number of spaces for indentation (0 for minified).\n * @default 2\n */\n indent?: number;\n}\n\n/**\n * Exported JSON structure.\n */\nexport interface ExportedJSON {\n /** Export metadata */\n exportedAt: string;\n\n /** Form information (if includeFormDefinition is true) */\n form?: {\n id: string;\n name: string;\n slug: string;\n fields: Array<{\n name: string;\n label: string;\n type: string;\n }>;\n };\n\n /** Total number of submissions in export */\n totalSubmissions: number;\n\n /** Array of submission data */\n submissions: Array<Record<string, unknown>>;\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/** UTF-8 BOM character sequence */\nconst UTF8_BOM = \"\\uFEFF\";\n\n// ============================================================================\n// Value Formatting Helpers\n// ============================================================================\n\n/**\n * Stringify a value safely, falling back to JSON for plain objects so we\n * never emit the default `[object Object]` representation.\n */\nfunction safeString(value: unknown): string {\n if (typeof value === \"object\" && value !== null) {\n return JSON.stringify(value);\n }\n return String(value);\n}\n\n/**\n * Format a field value for export based on field type.\n *\n * @param value - The raw value from submission data\n * @param field - The field definition (optional, for type-specific formatting)\n * @returns Formatted string representation of the value\n *\n * @example\n * ```typescript\n * formatExportValue(true, { type: 'checkbox' }); // \"Yes\"\n * formatExportValue(['a', 'b'], { type: 'select' }); // \"a, b\"\n * formatExportValue(new Date(), { type: 'date' }); // \"2024-01-15\"\n * ```\n */\nexport function formatExportValue(value: unknown, field?: FormField): string {\n // Handle null/undefined\n if (value === null || value === undefined) {\n return \"\";\n }\n\n // Type-specific formatting when field definition is available\n if (field) {\n switch (field.type) {\n case \"checkbox\":\n return value ? \"Yes\" : \"No\";\n\n case \"select\":\n if (Array.isArray(value)) {\n return value.join(\", \");\n }\n return safeString(value);\n\n case \"file\":\n if (Array.isArray(value)) {\n // File URLs or IDs\n return value.map(v => safeString(v)).join(\", \");\n }\n return safeString(value);\n\n case \"date\":\n if (value instanceof Date) {\n return value.toISOString().split(\"T\")[0];\n }\n if (typeof value === \"string\") {\n // Already a date string, return as-is or normalize\n const parsed = new Date(value);\n if (!isNaN(parsed.getTime())) {\n return parsed.toISOString().split(\"T\")[0];\n }\n }\n return safeString(value);\n\n case \"time\":\n return safeString(value);\n\n case \"hidden\":\n return safeString(value);\n\n default:\n // text, email, phone, url, textarea, number, radio\n break;\n }\n }\n\n // Generic formatting for values without field definition\n if (Array.isArray(value)) {\n return value.join(\", \");\n }\n\n if (typeof value === \"boolean\") {\n return value ? \"Yes\" : \"No\";\n }\n\n if (value instanceof Date) {\n return value.toISOString();\n }\n\n return safeString(value);\n}\n\n/**\n * Format a date for export.\n *\n * @param date - Date value (Date object or string)\n * @param format - Output format (\"iso\" or \"locale\")\n * @returns Formatted date string\n */\nfunction formatDate(\n date: Date | string | undefined,\n format: \"iso\" | \"locale\" = \"iso\"\n): string {\n if (!date) return \"\";\n\n const d = typeof date === \"string\" ? new Date(date) : date;\n\n if (isNaN(d.getTime())) {\n return \"\";\n }\n\n return format === \"iso\" ? d.toISOString() : d.toLocaleString();\n}\n\n// ============================================================================\n// CSV Export\n// ============================================================================\n\n/**\n * Escape a value for CSV format (RFC 4180 compliant).\n *\n * Rules:\n * - Fields containing comma, double-quote, or newline must be quoted\n * - Double-quotes within fields must be escaped with another double-quote\n *\n * @param value - The value to escape\n * @param delimiter - The delimiter being used\n * @returns Properly escaped CSV value\n */\nfunction escapeCSVValue(value: string, delimiter: string = \",\"): string {\n // Check if quoting is needed\n const needsQuoting =\n value.includes(delimiter) ||\n value.includes('\"') ||\n value.includes(\"\\n\") ||\n value.includes(\"\\r\");\n\n if (!needsQuoting) {\n return value;\n }\n\n // Escape double quotes by doubling them\n const escaped = value.replace(/\"/g, '\"\"');\n return `\"${escaped}\"`;\n}\n\n/**\n * Export submissions to CSV format.\n *\n * Generates an RFC 4180 compliant CSV string with:\n * - Form field values as columns (in form definition order)\n * - Optional metadata columns at the end (_id, _status, _submittedAt, etc.)\n * - Proper escaping of special characters\n * - Optional UTF-8 BOM for Excel compatibility\n *\n * @param submissions - Array of submission documents to export\n * @param form - Form document containing field definitions\n * @param options - Export options\n * @returns CSV formatted string\n *\n * @example\n * ```typescript\n * const csv = exportToCSV(submissions, form, {\n * includeMetadata: true,\n * includeBOM: true,\n * });\n *\n * // Download in browser\n * downloadFile(csv, 'submissions.csv', 'text/csv;charset=utf-8');\n * ```\n */\nexport function exportToCSV(\n submissions: SubmissionDocument[],\n form: FormDocument,\n options: CSVExportOptions = {}\n): string {\n const {\n includeMetadata = true,\n delimiter = \",\",\n includeBOM = true,\n dateFormat = \"iso\",\n } = options;\n\n const exportableFields = form.fields;\n\n // Build header row\n const headers: string[] = [];\n\n // Form field headers (using labels)\n for (const field of exportableFields) {\n headers.push(field.label);\n }\n\n // Metadata headers\n if (includeMetadata) {\n headers.push(\"ID\", \"Status\", \"Submitted At\", \"IP Address\", \"User Agent\");\n }\n\n // Build data rows\n const rows: string[][] = [];\n\n for (const submission of submissions) {\n const row: string[] = [];\n const data = submission.data;\n\n // Form field values\n for (const field of exportableFields) {\n const value = data[field.name];\n const formatted = formatExportValue(value, field);\n row.push(escapeCSVValue(formatted, delimiter));\n }\n\n // Metadata values\n if (includeMetadata) {\n row.push(escapeCSVValue(submission.id, delimiter));\n row.push(escapeCSVValue(submission.status, delimiter));\n row.push(\n escapeCSVValue(\n formatDate(submission.submittedAt, dateFormat),\n delimiter\n )\n );\n row.push(escapeCSVValue(submission.ipAddress || \"\", delimiter));\n row.push(escapeCSVValue(submission.userAgent || \"\", delimiter));\n }\n\n rows.push(row);\n }\n\n // Escape headers\n const escapedHeaders = headers.map(h => escapeCSVValue(h, delimiter));\n\n // Build CSV string\n const lines = [\n escapedHeaders.join(delimiter),\n ...rows.map(row => row.join(delimiter)),\n ];\n\n // Use CRLF line endings for maximum compatibility\n const csvContent = lines.join(\"\\r\\n\");\n\n // Prepend BOM if requested\n return includeBOM ? UTF8_BOM + csvContent : csvContent;\n}\n\n// ============================================================================\n// JSON Export\n// ============================================================================\n\n/**\n * Export submissions to JSON format.\n *\n * Generates a structured JSON export with:\n * - Export metadata (timestamp)\n * - Optional form definition\n * - Array of submissions with field values\n * - Optional submission metadata (_id, _status, etc.)\n *\n * @param submissions - Array of submission documents to export\n * @param form - Form document containing field definitions\n * @param options - Export options\n * @returns JSON formatted string\n *\n * @example\n * ```typescript\n * const json = exportToJSON(submissions, form, {\n * includeMetadata: true,\n * includeFormDefinition: true,\n * indent: 2,\n * });\n *\n * // Download in browser\n * downloadFile(json, 'submissions.json', 'application/json');\n * ```\n */\nexport function exportToJSON(\n submissions: SubmissionDocument[],\n form: FormDocument,\n options: JSONExportOptions = {}\n): string {\n const {\n includeMetadata = true,\n includeFormDefinition = true,\n indent = 2,\n } = options;\n\n const exportableFields = form.fields;\n\n // Build form definition\n const formDefinition = includeFormDefinition\n ? {\n id: form.id,\n name: form.name,\n slug: form.slug,\n fields: exportableFields.map(f => ({\n name: f.name,\n label: f.label,\n type: f.type,\n })),\n }\n : undefined;\n\n // Build submission data\n const exportedSubmissions = submissions.map(submission => {\n const data = submission.data;\n\n // Build field data object with labels as keys for readability\n const fieldData: Record<string, unknown> = {};\n\n for (const field of exportableFields) {\n const value = data[field.name];\n // Use field name as key (consistent with submission data)\n fieldData[field.name] = value;\n }\n\n // Add metadata if requested\n if (includeMetadata) {\n return {\n ...fieldData,\n _metadata: {\n id: submission.id,\n status: submission.status,\n submittedAt: submission.submittedAt,\n ipAddress: submission.ipAddress || null,\n userAgent: submission.userAgent || null,\n createdAt: submission.createdAt,\n updatedAt: submission.updatedAt,\n },\n };\n }\n\n return fieldData;\n });\n\n // Build export object\n const exportData: ExportedJSON = {\n exportedAt: new Date().toISOString(),\n totalSubmissions: submissions.length,\n submissions: exportedSubmissions,\n };\n\n // Add form definition if requested\n if (formDefinition) {\n exportData.form = formDefinition;\n }\n\n return JSON.stringify(exportData, null, indent);\n}\n\n// ============================================================================\n// Browser Download Helper\n// ============================================================================\n\n/**\n * Trigger a file download in the browser.\n *\n * Creates a Blob from the content and triggers a download using a temporary\n * anchor element. This function is browser-only and will throw in Node.js.\n *\n * @param content - File content as string\n * @param filename - Name for the downloaded file\n * @param mimeType - MIME type of the file\n *\n * @example\n * ```typescript\n * const csv = exportToCSV(submissions, form);\n * downloadFile(csv, 'contact-form-submissions.csv', 'text/csv;charset=utf-8');\n *\n * const json = exportToJSON(submissions, form);\n * downloadFile(json, 'submissions.json', 'application/json');\n * ```\n */\nexport function downloadFile(\n content: string,\n filename: string,\n mimeType: string\n): void {\n // Check for browser environment\n if (typeof window === \"undefined\" || typeof document === \"undefined\") {\n throw new Error(\"downloadFile is only available in browser environments\");\n }\n\n // Create blob\n const blob = new Blob([content], { type: mimeType });\n\n // Create download URL\n const url = URL.createObjectURL(blob);\n\n // Create temporary anchor element\n const link = document.createElement(\"a\");\n link.href = url;\n link.download = filename;\n\n // Trigger download\n document.body.appendChild(link);\n link.click();\n\n // Cleanup\n document.body.removeChild(link);\n URL.revokeObjectURL(url);\n}\n\n/**\n * Generate a filename for export.\n *\n * Creates a filename with form slug and current timestamp.\n *\n * @param formSlug - The form's slug\n * @param format - Export format ('csv' or 'json')\n * @returns Generated filename\n *\n * @example\n * ```typescript\n * generateExportFilename('contact-form', 'csv');\n * // Returns: \"contact-form-submissions-2024-01-15.csv\"\n * ```\n */\nexport function generateExportFilename(\n formSlug: string,\n format: \"csv\" | \"json\"\n): string {\n const date = new Date().toISOString().split(\"T\")[0];\n return `${formSlug}-submissions-${date}.${format}`;\n}\n\n// ============================================================================\n// Convenience Export Functions\n// ============================================================================\n\n/**\n * Export submissions and trigger browser download.\n *\n * Convenience function that combines export and download in one call.\n * Browser-only.\n *\n * @param submissions - Array of submission documents\n * @param form - Form document\n * @param format - Export format ('csv' or 'json')\n * @param options - Format-specific options\n *\n * @example\n * ```typescript\n * // In a React component\n * const handleExport = (format: 'csv' | 'json') => {\n * exportAndDownload(submissions, form, format);\n * };\n * ```\n */\nexport function exportAndDownload(\n submissions: SubmissionDocument[],\n form: FormDocument,\n format: \"csv\" | \"json\",\n options?: CSVExportOptions | JSONExportOptions\n): void {\n const filename = generateExportFilename(form.slug, format);\n\n if (format === \"csv\") {\n const content = exportToCSV(submissions, form, options);\n downloadFile(content, filename, \"text/csv;charset=utf-8\");\n } else {\n const content = exportToJSON(submissions, form, options);\n downloadFile(content, filename, \"application/json\");\n }\n}\n","/**\n * Spam Detection\n *\n * Implements honeypot and rate limiting spam protection for form submissions.\n * Provides a pluggable approach to spam detection that can be extended.\n *\n * @module handlers/spam-detection\n * @since 0.1.0\n */\n\n// ============================================================\n// Types\n// ============================================================\n\n/**\n * Configuration for spam detection.\n */\nexport interface SpamCheckConfig {\n /**\n * Enable honeypot field detection.\n * When enabled, submissions with filled honeypot fields are marked as spam.\n */\n honeypot?: boolean;\n\n /**\n * Rate limiting configuration.\n */\n rateLimit?: {\n /** Maximum submissions allowed per window */\n maxSubmissions: number;\n /** Time window in milliseconds */\n windowMs: number;\n };\n\n /**\n * reCAPTCHA configuration (for future implementation).\n */\n recaptcha?: {\n enabled: boolean;\n secretKey?: string;\n scoreThreshold?: number;\n };\n}\n\n/**\n * Options for spam check.\n */\nexport interface SpamCheckOptions {\n /** Form submission data */\n data: Record<string, unknown>;\n\n /** Submitter's IP address */\n ipAddress?: string;\n\n /** Form slug for rate limiting key */\n formSlug: string;\n\n /** Spam protection configuration */\n config: SpamCheckConfig;\n}\n\n/**\n * Result of spam check.\n */\nexport interface SpamCheckResult {\n /** Whether the submission is detected as spam */\n isSpam: boolean;\n\n /** Reason for spam detection (for logging, not exposed to user) */\n reason?: \"honeypot\" | \"rate_limit\" | \"recaptcha\";\n\n /** Additional details (for logging) */\n details?: string;\n}\n\n// ============================================================\n// Rate Limit Store\n// ============================================================\n\n/**\n * In-memory rate limit store.\n *\n * Note: For multi-instance deployments (serverless, load-balanced),\n * consider using Redis or a database-backed store instead.\n * This in-memory implementation works well for single-instance deployments.\n */\ninterface RateLimitEntry {\n count: number;\n timestamp: number;\n}\n\nconst rateLimitStore = new Map<string, RateLimitEntry>();\n\n// ============================================================\n// Main Spam Check Function\n// ============================================================\n\n/**\n * Check a form submission for spam indicators.\n *\n * Performs multiple spam checks based on configuration:\n * 1. Honeypot field detection\n * 2. Rate limiting by IP/form\n * 3. reCAPTCHA verification (future)\n *\n * @param options - Spam check options\n * @returns Spam check result\n *\n * @example\n * ```typescript\n * const result = await checkSpam({\n * data: formData,\n * ipAddress: req.ip,\n * formSlug: 'contact-form',\n * config: {\n * honeypot: true,\n * rateLimit: { maxSubmissions: 5, windowMs: 60000 },\n * },\n * });\n *\n * if (result.isSpam) {\n * // Silently reject (return fake success to bot)\n * return { success: true };\n * }\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/require-await -- public API: declared return type is `Promise<SpamCheckResult>` and callers `await` it; removing `async` would force a return-type change\nexport async function checkSpam(\n options: SpamCheckOptions\n): Promise<SpamCheckResult> {\n const { data, ipAddress, formSlug, config } = options;\n\n // 1. Honeypot check\n if (config.honeypot) {\n const honeypotResult = checkHoneypot(data);\n if (honeypotResult.isSpam) {\n return honeypotResult;\n }\n }\n\n // 2. Rate limiting check\n if (config.rateLimit && ipAddress) {\n const rateLimitResult = checkRateLimit(\n ipAddress,\n formSlug,\n config.rateLimit\n );\n if (rateLimitResult.isSpam) {\n return rateLimitResult;\n }\n }\n\n return { isSpam: false };\n}\n\n// ============================================================\n// Honeypot Detection\n// ============================================================\n\n/**\n * Standard honeypot field names to check.\n * These fields should be hidden from users but filled by bots.\n */\nconst HONEYPOT_FIELDS = [\n \"__honeypot\",\n \"_honeypot\",\n \"honeypot\",\n \"__hp\",\n \"_hp\",\n \"website\", // Common honeypot field name\n \"url_field\", // Another common honeypot\n \"fax_number\", // Rarely used by humans\n];\n\n/**\n * Check for honeypot field presence.\n *\n * Honeypot fields are hidden form fields that legitimate users\n * won't see or fill, but bots often fill automatically.\n *\n * @param data - Form submission data\n * @returns Spam check result\n */\nfunction checkHoneypot(data: Record<string, unknown>): SpamCheckResult {\n for (const fieldName of HONEYPOT_FIELDS) {\n const value = data[fieldName];\n\n // Check if honeypot field has a non-empty value\n if (value !== undefined && value !== null && value !== \"\") {\n let raw: string;\n if (typeof value === \"object\") {\n raw = JSON.stringify(value);\n } else {\n // eslint-disable-next-line @typescript-eslint/no-base-to-string -- value narrowed to primitive above; rule doesn't follow control flow on unknown\n raw = String(value);\n }\n const stringValue = raw.trim();\n if (stringValue.length > 0) {\n return {\n isSpam: true,\n reason: \"honeypot\",\n details: `Honeypot field \"${fieldName}\" was filled`,\n };\n }\n }\n }\n\n return { isSpam: false };\n}\n\n// ============================================================\n// Rate Limiting\n// ============================================================\n\n/**\n * Check rate limit for a given IP and form combination.\n *\n * Uses a sliding window approach where submissions are counted\n * within a time window. If the count exceeds the maximum,\n * further submissions are blocked.\n *\n * @param ipAddress - Submitter's IP address\n * @param formSlug - Form identifier\n * @param config - Rate limit configuration\n * @returns Spam check result\n */\nfunction checkRateLimit(\n ipAddress: string,\n formSlug: string,\n config: { maxSubmissions: number; windowMs: number }\n): SpamCheckResult {\n const key = `${formSlug}:${ipAddress}`;\n const now = Date.now();\n const entry = rateLimitStore.get(key);\n\n if (entry) {\n // Check if window has expired\n if (now - entry.timestamp > config.windowMs) {\n // Reset counter for new window\n rateLimitStore.set(key, { count: 1, timestamp: now });\n return { isSpam: false };\n }\n\n // Check if limit exceeded\n if (entry.count >= config.maxSubmissions) {\n return {\n isSpam: true,\n reason: \"rate_limit\",\n details: `Rate limit exceeded: ${entry.count}/${config.maxSubmissions} in ${config.windowMs}ms`,\n };\n }\n\n // Increment counter\n rateLimitStore.set(key, {\n count: entry.count + 1,\n timestamp: entry.timestamp,\n });\n } else {\n // First submission from this IP/form\n rateLimitStore.set(key, { count: 1, timestamp: now });\n }\n\n return { isSpam: false };\n}\n\n// ============================================================\n// Utility Functions\n// ============================================================\n\n/**\n * Clean up expired rate limit entries.\n *\n * Call this periodically (e.g., every 5 minutes) to prevent\n * memory leaks from accumulated rate limit entries.\n *\n * @param maxAgeMs - Maximum age of entries to keep (default: 5 minutes)\n * @returns Number of entries removed\n *\n * @example\n * ```typescript\n * // Set up periodic cleanup\n * setInterval(() => {\n * const removed = cleanupRateLimitStore();\n * console.log(`Cleaned up ${removed} expired rate limit entries`);\n * }, 5 * 60 * 1000); // Every 5 minutes\n * ```\n */\nexport function cleanupRateLimitStore(\n maxAgeMs: number = 5 * 60 * 1000\n): number {\n const now = Date.now();\n let removed = 0;\n\n for (const [key, entry] of rateLimitStore.entries()) {\n if (now - entry.timestamp > maxAgeMs) {\n rateLimitStore.delete(key);\n removed++;\n }\n }\n\n return removed;\n}\n\n/**\n * Get the current size of the rate limit store.\n *\n * Useful for monitoring and debugging.\n *\n * @returns Number of entries in the store\n */\nexport function getRateLimitStoreSize(): number {\n return rateLimitStore.size;\n}\n\n/**\n * Clear all rate limit entries.\n *\n * Useful for testing or resetting state.\n */\nexport function clearRateLimitStore(): void {\n rateLimitStore.clear();\n}\n\n/**\n * Check if a specific IP/form is currently rate limited.\n *\n * @param ipAddress - IP address to check\n * @param formSlug - Form slug\n * @param config - Rate limit configuration\n * @returns Whether the IP is currently rate limited\n */\nexport function isRateLimited(\n ipAddress: string,\n formSlug: string,\n config: { maxSubmissions: number; windowMs: number }\n): boolean {\n const key = `${formSlug}:${ipAddress}`;\n const now = Date.now();\n const entry = rateLimitStore.get(key);\n\n if (!entry) {\n return false;\n }\n\n // Check if window has expired\n if (now - entry.timestamp > config.windowMs) {\n return false;\n }\n\n return entry.count >= config.maxSubmissions;\n}\n\n// ============================================================\n// reCAPTCHA Support (Future Implementation)\n// ============================================================\n\n/**\n * Verify reCAPTCHA v3 token.\n *\n * Note: This is a placeholder for future implementation.\n * reCAPTCHA verification requires server-side API calls to Google.\n *\n * @param data - Form data containing reCAPTCHA token\n * @param config - reCAPTCHA configuration\n * @returns Spam check result\n */\n// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/require-await -- declared return type is `Promise<SpamCheckResult>`; placeholder for unimplemented Google siteverify fetch\nasync function verifyRecaptcha(\n _data: Record<string, unknown>,\n _config: { secretKey?: string; scoreThreshold?: number }\n): Promise<SpamCheckResult> {\n // Placeholder - always pass for now\n return { isSpam: false };\n}\n","/**\n * Form Submission Handler\n *\n * Processes form submissions including validation, spam detection,\n * storage, and notification triggering.\n *\n * @module handlers/submit-form\n * @since 0.1.0\n */\n\nimport type { PluginContext } from \"nextly\";\n\nimport type {\n FormDocument,\n SubmissionDocument,\n ResolvedFormBuilderConfig,\n FormField,\n} from \"../types\";\nimport {\n generateZodSchema,\n transformFormData,\n getValidationErrors,\n} from \"../utils/generate-schema\";\n\nimport { checkSpam } from \"./spam-detection\";\n\n// ============================================================\n// Types\n// ============================================================\n\n/**\n * Options for submitting a form.\n */\nexport interface SubmitFormOptions {\n /** Form slug (URL-friendly identifier) */\n formSlug: string;\n\n /** Form submission data */\n data: Record<string, unknown>;\n\n /** Optional metadata about the submission */\n metadata?: {\n /** Submitter's IP address */\n ipAddress?: string;\n /** Submitter's user agent string */\n userAgent?: string;\n };\n}\n\n/**\n * Result of form submission.\n */\nexport interface SubmitFormResult {\n /** Whether the submission was successful */\n success: boolean;\n\n /** The created submission document (on success) */\n submission?: SubmissionDocument;\n\n /** Error message (on failure) */\n error?: string;\n\n /** Field-level validation errors */\n validationErrors?: Record<string, string>;\n\n /** Redirect URL (if form configured for redirect on success) */\n redirect?: string;\n}\n\n/**\n * Context for the submission handler.\n */\nexport interface SubmitFormContext {\n /** Plugin context with access to services */\n pluginContext: PluginContext;\n\n /** Resolved plugin configuration */\n pluginConfig: ResolvedFormBuilderConfig;\n}\n\n// ============================================================\n// Main Submission Handler\n// ============================================================\n\n/**\n * Process a form submission.\n *\n * This is the main entry point for handling form submissions.\n * It performs the following steps:\n *\n * 1. Fetch form configuration by slug\n * 2. Verify form status (must be \"published\")\n * 3. Transform and validate submission data\n * 4. Check for spam (honeypot, rate limiting)\n * 5. Create submission record in database\n * 6. Return result with redirect info if configured\n *\n * Note: Email notifications are triggered separately via collection\n * hooks in the submissions collection (see Phase 4.2).\n *\n * @param options - Submission options\n * @param context - Handler context with services\n * @returns Submission result\n *\n * @example\n * ```typescript\n * const result = await submitForm(\n * {\n * formSlug: 'contact-form',\n * data: { name: 'John', email: 'john@example.com', message: 'Hello!' },\n * metadata: { ipAddress: '127.0.0.1', userAgent: 'Mozilla/5.0...' },\n * },\n * { pluginContext, pluginConfig }\n * );\n *\n * if (result.success) {\n * console.log('Submission created:', result.submission?.id);\n * if (result.redirect) {\n * // Redirect user\n * }\n * } else {\n * console.error('Submission failed:', result.error);\n * }\n * ```\n */\nexport async function submitForm(\n options: SubmitFormOptions,\n context: SubmitFormContext\n): Promise<SubmitFormResult> {\n const { formSlug, data, metadata } = options;\n const { pluginContext, pluginConfig } = context;\n const { collections } = pluginContext.services;\n const { logger } = pluginContext.infra;\n\n try {\n // 1. Fetch form configuration\n const form = await fetchFormBySlug(formSlug, pluginConfig, pluginContext);\n\n if (!form) {\n logger.warn?.(\"Form submission attempted for non-existent form\", {\n formSlug,\n });\n return {\n success: false,\n error: \"Form not found\",\n };\n }\n\n // 2. Check form status\n if (form.status !== \"published\") {\n logger.info?.(\"Form submission rejected - form not published\", {\n formSlug,\n status: form.status,\n });\n return {\n success: false,\n error: \"This form is not currently accepting submissions\",\n };\n }\n\n // 3. Transform and validate submission data\n const transformedData = transformFormData(data, form.fields);\n const schema = generateZodSchema(form.fields);\n const validationResult = schema.safeParse(transformedData);\n\n if (!validationResult.success) {\n const validationErrors = getValidationErrors(validationResult);\n logger.debug?.(\"Form validation failed\", {\n formSlug,\n errors: validationErrors,\n });\n return {\n success: false,\n error: \"Validation failed\",\n validationErrors,\n };\n }\n\n // 3b. Sanitize validated submission data (strip HTML from free-text fields)\n sanitizeSubmissionData(validationResult.data, form.fields);\n\n // 4. Spam detection\n const spamResult = await checkSpam({\n data: transformedData,\n ipAddress: metadata?.ipAddress,\n formSlug,\n config: {\n honeypot: pluginConfig.spamProtection.honeypot,\n rateLimit: pluginConfig.spamProtection.rateLimit,\n recaptcha: pluginConfig.spamProtection.recaptcha,\n },\n });\n\n if (spamResult.isSpam) {\n // Silently reject spam - return fake success to avoid tipping off bots\n logger.info?.(\"Spam submission detected and rejected\", {\n formSlug,\n reason: spamResult.reason,\n ipAddress: metadata?.ipAddress,\n });\n\n // Return fake success (don't tell spammers their submission was rejected)\n return {\n success: true,\n };\n }\n\n // 5. Create submission record\n const submissionData = {\n form: form.id,\n data: validationResult.data,\n status: \"new\" as const,\n ipAddress: metadata?.ipAddress || null,\n userAgent: metadata?.userAgent || null,\n submittedAt: new Date(),\n };\n\n const submission = await collections.createEntry(\n pluginConfig.formSubmissionOverrides.slug,\n submissionData,\n {} // Empty context - submission creation is public\n );\n\n logger.info?.(\"Form submission created successfully\", {\n formSlug,\n submissionId: submission.id,\n });\n\n // Email notifications are sent via the afterCreate hook registered in\n // plugin.ts init(), so they fire for all submission paths (HTTP + direct).\n\n // 6. Determine redirect URL\n const redirect = determineRedirectUrl(form);\n\n return {\n success: true,\n submission: submission as unknown as SubmissionDocument,\n redirect,\n };\n } catch (error) {\n logger.error?.(\"Form submission error\", {\n formSlug,\n error: error instanceof Error ? error.message : String(error),\n });\n\n return {\n success: false,\n error: \"An error occurred processing your submission. Please try again.\",\n };\n }\n}\n\n// ============================================================\n// Helper Functions\n// ============================================================\n\n/**\n * Fetch a form by its slug.\n *\n * Uses listEntries and filters by slug client-side.\n * Note: For production with large numbers of forms, consider using\n * direct database queries with WHERE clause support.\n *\n * @param slug - Form slug\n * @param pluginConfig - Plugin configuration\n * @param pluginContext - Plugin context\n * @returns Form document or null if not found\n */\nasync function fetchFormBySlug(\n slug: string,\n pluginConfig: ResolvedFormBuilderConfig,\n pluginContext: PluginContext\n): Promise<FormDocument | null> {\n try {\n const { collections } = pluginContext.services;\n\n // List all forms and filter by slug\n // Note: This approach works for small-medium form counts.\n // For large-scale deployments, use direct database queries.\n const result = await collections.listEntries(\n pluginConfig.formOverrides.slug,\n {\n pagination: { limit: 100 }, // Fetch enough to find the form\n },\n {} // Empty context for public access\n );\n\n if (!result.data || result.data.length === 0) {\n return null;\n }\n\n // Find form by slug\n const form = result.data.find(\n (entry: unknown) => (entry as FormDocument).slug === slug\n );\n\n return form ? (form as unknown as FormDocument) : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Determine the redirect URL based on form settings.\n *\n * @param form - Form document\n * @returns Redirect URL or undefined\n */\nfunction determineRedirectUrl(form: FormDocument): string | undefined {\n if (!form.settings) {\n return undefined;\n }\n\n // Check confirmation type\n if (form.settings.confirmationType !== \"redirect\") {\n return undefined;\n }\n\n // Direct URL redirect\n if (form.settings.redirectUrl) {\n return form.settings.redirectUrl;\n }\n\n // Relationship-based redirect (requires additional lookup)\n // Note: For relationship redirects, the admin UI should resolve\n // the URL before saving to redirectUrl, or the frontend should\n // handle the relationship lookup.\n if (form.settings.redirectRelation) {\n // Return a placeholder that the frontend can resolve\n // Format: relationship://{collection}/{id}\n const { relationTo, value } = form.settings.redirectRelation;\n return `relationship://${relationTo}/${value}`;\n }\n\n return undefined;\n}\n\n// ============================================================\n// Validation-Only Function\n// ============================================================\n\n/**\n * Validate form data without creating a submission.\n *\n * Useful for real-time validation in the frontend.\n *\n * @param formSlug - Form slug\n * @param data - Form data to validate\n * @param context - Handler context\n * @returns Validation result\n *\n * @example\n * ```typescript\n * const result = await validateSubmission(\n * 'contact-form',\n * { name: 'John', email: 'invalid-email' },\n * { pluginContext, pluginConfig }\n * );\n *\n * if (!result.valid) {\n * console.log('Validation errors:', result.errors);\n * }\n * ```\n */\nexport async function validateSubmission(\n formSlug: string,\n data: Record<string, unknown>,\n context: SubmitFormContext\n): Promise<{ valid: boolean; errors?: Record<string, string> }> {\n const { pluginContext, pluginConfig } = context;\n\n // Fetch form\n const form = await fetchFormBySlug(formSlug, pluginConfig, pluginContext);\n\n if (!form) {\n return { valid: false, errors: { _form: \"Form not found\" } };\n }\n\n // Transform and validate\n const transformedData = transformFormData(data, form.fields);\n const schema = generateZodSchema(form.fields);\n const result = schema.safeParse(transformedData);\n\n if (result.success) {\n return { valid: true };\n }\n\n return {\n valid: false,\n errors: getValidationErrors(result),\n };\n}\n\n// ============================================================\n// Form Status Check\n// ============================================================\n\n/**\n * Check if a form is accepting submissions.\n *\n * @param formSlug - Form slug\n * @param context - Handler context\n * @returns Whether the form accepts submissions\n */\nexport async function isFormAcceptingSubmissions(\n formSlug: string,\n context: SubmitFormContext\n): Promise<boolean> {\n const { pluginContext, pluginConfig } = context;\n\n const form = await fetchFormBySlug(formSlug, pluginConfig, pluginContext);\n\n if (!form) {\n return false;\n }\n\n return form.status === \"published\";\n}\n\n// ============================================================\n// Submission Data Sanitization\n// ============================================================\n\n/**\n * Form field types that accept free-text string input from end users.\n * These fields can contain HTML injection vectors and must be sanitized.\n *\n * Fields NOT in this set (select, radio, checkbox, number, date, time, file)\n * are constrained by Zod enum/type validation and don't need sanitization.\n */\nconst TEXT_FORM_FIELDS = new Set([\n \"text\",\n \"email\",\n \"textarea\",\n \"phone\",\n \"url\",\n \"hidden\",\n]);\n\n/**\n * Remove all HTML tags from a string, collapse whitespace, and trim.\n *\n * Uses a regex that matches both complete tags (`<b>`) and unclosed tags\n * at end-of-string (`<script`) to prevent browsers from interpreting\n * incomplete markup.\n */\nfunction stripHtmlTags(input: string): string {\n return input\n .replace(/<[^>]*(?:>|$)/g, \"\")\n .replace(/\\s+/g, \" \")\n .trim();\n}\n\n/**\n * Sanitize form submission data by stripping HTML tags from free-text fields.\n *\n * Iterates over the form's field definitions and applies `stripHtmlTags()`\n * to values whose field type is in `TEXT_FORM_FIELDS`. Non-string values\n * and constrained fields (select, radio, checkbox, etc.) are left unchanged.\n *\n * Mutates the data object in place for efficiency.\n *\n * @param data - Validated submission data (mutated in place)\n * @param fields - Form field definitions (used for type-aware dispatch)\n */\nfunction sanitizeSubmissionData(\n data: Record<string, unknown>,\n fields: FormField[]\n): void {\n for (const field of fields) {\n if (!TEXT_FORM_FIELDS.has(field.type)) continue;\n\n const value = data[field.name];\n if (typeof value !== \"string\") continue;\n\n data[field.name] = stripHtmlTags(value);\n }\n}\n\n// ============================================================\n// Bulk Operations (Admin)\n// ============================================================\n\n/**\n * Get submission statistics for a form.\n *\n * Note: This function uses client-side filtering which works for\n * small-medium submission counts. For large-scale deployments,\n * consider using direct database queries with COUNT aggregation.\n *\n * @param formSlug - Form slug\n * @param context - Handler context\n * @returns Submission statistics\n */\nexport async function getFormSubmissionStats(\n formSlug: string,\n context: SubmitFormContext\n): Promise<{\n total: number;\n new: number;\n read: number;\n archived: number;\n} | null> {\n const { pluginContext, pluginConfig } = context;\n const { collections } = pluginContext.services;\n\n // Get form ID\n const form = await fetchFormBySlug(formSlug, pluginConfig, pluginContext);\n if (!form) {\n return null;\n }\n\n try {\n // Fetch all submissions for this form\n // Note: For large-scale use, implement pagination or direct DB queries\n const result = await collections.listEntries(\n pluginConfig.formSubmissionOverrides.slug,\n {\n pagination: { limit: 1000 }, // Reasonable limit for stats\n },\n {}\n );\n\n if (!result.data) {\n return { total: 0, new: 0, read: 0, archived: 0 };\n }\n\n // Filter submissions for this form and count by status\n const submissions = result.data.filter(\n (entry: unknown) => (entry as { form: string }).form === form.id\n );\n\n const stats = {\n total: submissions.length,\n new: 0,\n read: 0,\n archived: 0,\n };\n\n for (const sub of submissions) {\n const status = (sub as unknown as { status: string }).status;\n if (status === \"new\") stats.new++;\n else if (status === \"read\") stats.read++;\n else if (status === \"archived\") stats.archived++;\n }\n\n return stats;\n } catch {\n return null;\n }\n}\n","/**\n * Webhook Handler\n *\n * Triggers webhook notifications for form events.\n * Webhooks are fired asynchronously and do not block form submission.\n *\n * @module handlers/webhooks\n * @since 0.1.0\n */\n\nimport { safeFetch, ExternalUrlError } from \"nextly\";\n\nimport type {\n FormDocument,\n SubmissionDocument,\n WebhookConfig,\n WebhookEvent,\n} from \"../types\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Webhook payload sent to configured endpoints.\n */\nexport interface WebhookPayload {\n /** Event type that triggered the webhook */\n event: WebhookEvent;\n\n /** ISO 8601 timestamp when the event occurred */\n timestamp: string;\n\n /** Form information */\n form: {\n id: string;\n slug: string;\n name: string;\n };\n\n /** Submission information (full data or minimal based on config) */\n submission:\n | SubmissionDocument\n | {\n id: string;\n submittedAt: Date;\n };\n}\n\n/**\n * Options for triggering webhooks.\n */\nexport interface TriggerWebhooksOptions {\n /** Event type */\n event: WebhookEvent;\n\n /** Form document */\n form: FormDocument;\n\n /** Submission document */\n submission: SubmissionDocument;\n}\n\n/**\n * Result of a single webhook delivery attempt.\n */\nexport interface WebhookDeliveryResult {\n /** Webhook URL */\n url: string;\n\n /** Whether the delivery was successful */\n success: boolean;\n\n /** HTTP status code (if request completed) */\n statusCode?: number;\n\n /** Error message (if delivery failed) */\n error?: string;\n\n /** Time taken in milliseconds */\n durationMs: number;\n}\n\n/**\n * Result of triggering webhooks for an event.\n */\nexport interface TriggerWebhooksResult {\n /** Number of webhooks triggered */\n triggered: number;\n\n /** Number of successful deliveries */\n successful: number;\n\n /** Number of failed deliveries */\n failed: number;\n\n /** Individual delivery results */\n results: WebhookDeliveryResult[];\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/** Default timeout for webhook requests (10 seconds) */\nconst WEBHOOK_TIMEOUT_MS = 10_000;\n\n/** Header name for webhook signature */\nconst SIGNATURE_HEADER = \"X-Webhook-Signature\";\n\n/** Header name for event type */\nconst EVENT_HEADER = \"X-Webhook-Event\";\n\n/** Header name for delivery ID (submission ID) */\nconst DELIVERY_ID_HEADER = \"X-Webhook-Delivery-ID\";\n\n// ============================================================================\n// Signature Generation\n// ============================================================================\n\n/**\n * Generate HMAC-SHA256 signature for webhook payload.\n *\n * @param payload - The webhook payload as a string\n * @param secret - The secret key for signing\n * @returns Signature in format \"sha256=<hex>\"\n */\nasync function generateSignature(\n payload: string,\n secret: string\n): Promise<string> {\n // Use Web Crypto API (works in both Node.js 18+ and browsers)\n const encoder = new TextEncoder();\n const keyData = encoder.encode(secret);\n const messageData = encoder.encode(payload);\n\n // Import the key\n const cryptoKey = await crypto.subtle.importKey(\n \"raw\",\n keyData,\n { name: \"HMAC\", hash: \"SHA-256\" },\n false,\n [\"sign\"]\n );\n\n // Sign the message\n const signature = await crypto.subtle.sign(\"HMAC\", cryptoKey, messageData);\n\n // Convert to hex string\n const hashArray = Array.from(new Uint8Array(signature));\n const hashHex = hashArray.map(b => b.toString(16).padStart(2, \"0\")).join(\"\");\n\n return `sha256=${hashHex}`;\n}\n\n// ============================================================================\n// Webhook Delivery\n// ============================================================================\n\n/**\n * Send a single webhook request.\n *\n * @param webhook - Webhook configuration\n * @param payload - Webhook payload\n * @returns Delivery result\n */\nasync function sendWebhook(\n webhook: WebhookConfig,\n payload: WebhookPayload\n): Promise<WebhookDeliveryResult> {\n const startTime = Date.now();\n const payloadString = JSON.stringify(payload);\n\n try {\n // Build headers\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n [EVENT_HEADER]: payload.event,\n [DELIVERY_ID_HEADER]: payload.submission.id,\n ...webhook.headers,\n };\n\n // Add signature if secret is configured\n if (webhook.secret) {\n headers[SIGNATURE_HEADER] = await generateSignature(\n payloadString,\n webhook.secret\n );\n }\n\n // Create abort controller for timeout\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), WEBHOOK_TIMEOUT_MS);\n\n try {\n const response = await safeFetch(webhook.url, {\n method: webhook.method || \"POST\",\n headers,\n body: payloadString,\n signal: controller.signal,\n allowLocalhost: process.env.NODE_ENV !== \"production\",\n });\n\n clearTimeout(timeoutId);\n\n const durationMs = Date.now() - startTime;\n\n // Consider 2xx status codes as success\n if (response.ok) {\n return {\n url: webhook.url,\n success: true,\n statusCode: response.status,\n durationMs,\n };\n }\n\n return {\n url: webhook.url,\n success: false,\n statusCode: response.status,\n error: `HTTP ${response.status}: ${response.statusText}`,\n durationMs,\n };\n } finally {\n clearTimeout(timeoutId);\n }\n } catch (error) {\n const durationMs = Date.now() - startTime;\n\n // Handle abort (timeout)\n if (error instanceof Error && error.name === \"AbortError\") {\n return {\n url: webhook.url,\n success: false,\n error: `Timeout after ${WEBHOOK_TIMEOUT_MS}ms`,\n durationMs,\n };\n }\n\n // The webhook URL pointed at an internal / non-public address\n // and was refused before the request left the host. Surface as a\n // distinct delivery failure.\n if (error instanceof ExternalUrlError) {\n return {\n url: webhook.url,\n success: false,\n error: `Webhook URL rejected for SSRF safety: ${error.message}`,\n durationMs,\n };\n }\n\n // Handle other errors\n return {\n url: webhook.url,\n success: false,\n error: error instanceof Error ? error.message : String(error),\n durationMs,\n };\n }\n}\n\n// ============================================================================\n// Main Webhook Trigger Function\n// ============================================================================\n\n/**\n * Trigger webhooks for a form event.\n *\n * This function fires all matching webhooks asynchronously and does not\n * block form submission. Failed webhooks are logged but do not cause\n * submission failures.\n *\n * @param options - Trigger options\n * @returns Result of webhook triggering\n *\n * @example\n * ```typescript\n * // After creating a submission\n * const result = await triggerWebhooks({\n * event: 'submission.created',\n * form,\n * submission,\n * });\n *\n * console.log(`Triggered ${result.triggered} webhooks`);\n * console.log(`Successful: ${result.successful}, Failed: ${result.failed}`);\n * ```\n */\nexport async function triggerWebhooks(\n options: TriggerWebhooksOptions\n): Promise<TriggerWebhooksResult> {\n const { event, form, submission } = options;\n\n // Get webhooks configured on the form\n const webhooks = form.webhooks || [];\n\n // Filter webhooks that subscribe to this event\n const relevantWebhooks = webhooks.filter(w => w.events.includes(event));\n\n if (relevantWebhooks.length === 0) {\n return {\n triggered: 0,\n successful: 0,\n failed: 0,\n results: [],\n };\n }\n\n // Build payloads and send webhooks in parallel\n const deliveryPromises = relevantWebhooks.map(async webhook => {\n // Build payload based on includeData setting\n const payload: WebhookPayload = {\n event,\n timestamp: new Date().toISOString(),\n form: {\n id: form.id,\n slug: form.slug,\n name: form.name,\n },\n submission:\n webhook.includeData !== false\n ? submission\n : {\n id: submission.id,\n submittedAt: submission.submittedAt,\n },\n };\n\n return sendWebhook(webhook, payload);\n });\n\n // Wait for all webhooks to complete (or fail)\n const results = await Promise.all(deliveryPromises);\n\n // Count successes and failures\n const successful = results.filter(r => r.success).length;\n const failed = results.filter(r => !r.success).length;\n\n return {\n triggered: relevantWebhooks.length,\n successful,\n failed,\n results,\n };\n}\n\n// ============================================================================\n// Fire-and-Forget Helper\n// ============================================================================\n\n/**\n * Trigger webhooks without waiting for completion.\n *\n * This is the recommended way to call webhooks from submission handlers\n * as it doesn't block the response. Errors are logged but not thrown.\n *\n * @param options - Trigger options\n * @param logger - Optional logger for error reporting\n *\n * @example\n * ```typescript\n * // In submission handler - fire and forget\n * fireWebhooks(\n * { event: 'submission.created', form, submission },\n * pluginContext.infra.logger\n * );\n *\n * // Continue with response immediately\n * return { success: true, submission };\n * ```\n */\nexport function fireWebhooks(\n options: TriggerWebhooksOptions,\n logger?: {\n info?: (message: string, data?: Record<string, unknown>) => void;\n warn?: (message: string, data?: Record<string, unknown>) => void;\n error?: (message: string, data?: Record<string, unknown>) => void;\n }\n): void {\n // Fire webhooks in background\n triggerWebhooks(options)\n .then(result => {\n if (result.triggered > 0) {\n if (result.failed > 0) {\n logger?.warn?.(\"Some webhooks failed\", {\n event: options.event,\n formSlug: options.form.slug,\n submissionId: options.submission.id,\n triggered: result.triggered,\n successful: result.successful,\n failed: result.failed,\n errors: result.results\n .filter(r => !r.success)\n .map(r => ({ url: r.url, error: r.error })),\n });\n } else {\n logger?.info?.(\"Webhooks triggered successfully\", {\n event: options.event,\n formSlug: options.form.slug,\n submissionId: options.submission.id,\n count: result.triggered,\n });\n }\n }\n })\n .catch(error => {\n // This shouldn't happen since triggerWebhooks catches all errors,\n // but handle it just in case\n logger?.error?.(\"Unexpected error triggering webhooks\", {\n event: options.event,\n formSlug: options.form.slug,\n submissionId: options.submission.id,\n error: error instanceof Error ? error.message : String(error),\n });\n });\n}\n\n// ============================================================================\n// Utility Functions\n// ============================================================================\n\n/**\n * Validate a webhook URL.\n *\n * @param url - URL to validate\n * @returns Whether the URL is valid for webhooks\n */\nexport function isValidWebhookUrl(url: string): boolean {\n try {\n const parsed = new URL(url);\n // Only allow HTTPS in production (HTTP allowed for localhost)\n if (\n parsed.protocol !== \"https:\" &&\n !parsed.hostname.match(/^(localhost|127\\.0\\.0\\.1)$/)\n ) {\n return false;\n }\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Get the list of supported webhook events.\n *\n * @returns Array of supported event types\n */\nexport function getSupportedWebhookEvents(): WebhookEvent[] {\n return [\"submission.created\", \"submission.updated\", \"submission.deleted\"];\n}\n","/**\n * @nextly/plugin-form-builder\n *\n * Visual form builder plugin for Nextly CMS.\n * Provides drag-and-drop form building, submission management,\n * email notifications, and spam protection.\n *\n * @packageDocumentation\n */\n\n// ============================================================\n// Main Plugin Export\n// ============================================================\n\n// ============================================================\n// Default Plugin Instance\n// ============================================================\n\n/**\n * Pre-configured form builder plugin for simple usage.\n *\n * This is a default instance with all defaults applied.\n * For customization, use `formBuilder()` instead.\n *\n * @example Simple usage - just add the plugin!\n * ```typescript\n * // nextly.config.ts\n * import { defineConfig } from 'nextly';\n * import { formBuilderPlugin } from '@nextly/plugin-form-builder';\n *\n * export default defineConfig({\n * plugins: [formBuilderPlugin],\n * collections: [Posts, Users, Media], // Your collections only\n * });\n * ```\n *\n * @example With customization\n * ```typescript\n * import { formBuilder } from '@nextly/plugin-form-builder';\n *\n * const myFormPlugin = formBuilder({\n * notifications: { defaultFrom: 'noreply@example.com' },\n * });\n *\n * export default defineConfig({\n * plugins: [myFormPlugin.plugin],\n * });\n * ```\n */\nimport { formBuilder } from \"./plugin\";\n\nexport {\n formBuilder,\n getFormBuilderConfig,\n type NextlyPlugin,\n type FormBuilderPluginResult,\n} from \"./plugin\";\nexport const formBuilderPlugin = formBuilder().plugin;\n\n// ============================================================\n// Type Exports\n// ============================================================\n\n// Plugin configuration types\nexport type {\n FormBuilderPluginOptions,\n ResolvedFormBuilderConfig,\n FieldBlockConfig,\n} from \"./types\";\n\n// Form field types (discriminated union)\nexport type {\n FormField,\n FormFieldType,\n BaseFormField,\n TextFormField,\n EmailFormField,\n NumberFormField,\n PhoneFormField,\n UrlFormField,\n TextareaFormField,\n SelectFormField,\n CheckboxFormField,\n RadioFormField,\n FileFormField,\n DateFormField,\n TimeFormField,\n HiddenFormField,\n} from \"./types\";\n\n// Form configuration types\nexport type {\n FormConfig,\n FormSettings,\n FormNotifications,\n FormAccess,\n FormHooks,\n FormHookContext,\n} from \"./types\";\n\n// Document types\nexport type { FormDocument, SubmissionDocument, FormSubmission } from \"./types\";\n\n// Validation and conditional logic types\nexport type {\n ValidationRules,\n ConditionalLogic,\n ConditionalLogicCondition,\n} from \"./types\";\n\n// Email types\nexport type { EmailConfig } from \"./types\";\n\n// Webhook types\nexport type { WebhookConfig, WebhookEvent } from \"./types\";\n\n// ============================================================\n// Utility Exports\n// ============================================================\n\n// Type guards\nexport {\n isFormField,\n isFormFieldType,\n isTextFormField,\n isEmailFormField,\n isSelectFormField,\n isCheckboxFormField,\n isFileFormField,\n} from \"./types\";\n\n// ============================================================\n// Config Utilities (validation & defaults)\n// ============================================================\n\n// Validation utilities\nexport {\n validateFormConfig,\n assertValidFormConfig,\n RESERVED_FORM_SLUGS,\n type FormValidationError,\n type FormValidationErrorCode,\n type FormValidationResult,\n} from \"./config\";\n\n// Default application utilities\nexport {\n applyFormDefaults,\n applyFieldDefaults,\n createFormConfig,\n toTitleCase,\n pluralize,\n DEFAULT_FORM_SETTINGS,\n DEFAULT_NOTIFICATION_SETTINGS,\n} from \"./config\";\n\n// ============================================================\n// Field Helper Functions\n// ============================================================\n\n// Utility helper\nexport { option, type FormFieldOption } from \"./config\";\n\n// Text input field helpers\nexport { text, email, phone, url, textarea } from \"./config\";\n\n// Numeric field helpers\nexport { number } from \"./config\";\n\n// Selection field helpers\nexport { select, checkbox, radio } from \"./config\";\n\n// File field helpers\nexport { file } from \"./config\";\n\n// Date/time field helpers\nexport { date, time } from \"./config\";\n\n// Special field helpers\nexport { hidden } from \"./config\";\n\n// ============================================================\n// Conditional Logic Utilities\n// ============================================================\n\n// Conditional logic evaluation\nexport {\n evaluateConditions,\n isValidComparisonOperator,\n getSupportedComparisonOperators,\n type ComparisonOperator,\n} from \"./utils\";\n\n// ============================================================\n// Schema Generation & Validation Utilities\n// ============================================================\n\n// Zod schema generation for form validation\nexport {\n generateZodSchema,\n transformFormData,\n validateFormData,\n getValidationErrors,\n} from \"./utils\";\n\n// ============================================================\n// Export Utilities (CSV, JSON)\n// ============================================================\n\n// Export format utilities\nexport {\n exportToCSV,\n exportToJSON,\n formatExportValue,\n downloadFile,\n generateExportFilename,\n exportAndDownload,\n type CSVExportOptions,\n type JSONExportOptions,\n type ExportedJSON,\n} from \"./utils\";\n\n// ============================================================\n// Form Submission Handlers\n// ============================================================\n\n// Main submission handler\nexport {\n submitForm,\n validateSubmission,\n isFormAcceptingSubmissions,\n getFormSubmissionStats,\n type SubmitFormOptions,\n type SubmitFormResult,\n type SubmitFormContext,\n} from \"./handlers\";\n\n// Spam detection utilities\nexport {\n checkSpam,\n cleanupRateLimitStore,\n getRateLimitStoreSize,\n clearRateLimitStore,\n isRateLimited,\n type SpamCheckConfig,\n type SpamCheckOptions,\n type SpamCheckResult,\n} from \"./handlers\";\n\n// Webhook utilities\nexport {\n triggerWebhooks,\n fireWebhooks,\n isValidWebhookUrl,\n getSupportedWebhookEvents,\n type WebhookPayload,\n type TriggerWebhooksOptions,\n type WebhookDeliveryResult,\n type TriggerWebhooksResult,\n} from \"./handlers\";\n\n// ============================================================\n// Collection Exports (for advanced usage)\n// ============================================================\n\nexport { formsCollection, submissionsCollection } from \"./collections\";\n\n// ============================================================\n// Component Exports\n// ============================================================\n// Note: React components are exported from `@nextly/plugin-form-builder/components`\n// to avoid \"use client\" issues when importing the plugin in server contexts.\n// Use: import { SubmissionList, SubmissionDetail } from '@nextly/plugin-form-builder/components';\n\n// Type-only exports are safe for server contexts\nexport type { SubmissionListProps, SubmissionDetailProps } from \"./components\";\n"],"mappings":"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","names":["text","textarea","select","json","relationship","text","textarea","select","checkbox","date","schema","date","date","url","url"]}