@nextlyhq/plugin-form-builder 0.0.2-alpha.51 → 0.0.2-alpha.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin/index.d.ts +1 -1
- package/dist/admin/index.js +1 -1
- package/dist/{chunk-WVI7N3QO.js → chunk-XCICDELK.js} +1 -1
- package/dist/chunk-XCICDELK.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +22 -12
- package/dist/index.js.map +1 -1
- package/dist/{types-r8uBeB0u.d.ts → types-DuEFdLNU.d.ts} +21 -3
- package/package.json +10 -10
- package/dist/chunk-WVI7N3QO.js.map +0 -1
package/dist/admin/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ComponentPath } from '@nextlyhq/plugin-sdk/admin';
|
|
2
2
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
3
|
-
import { q as FormField, s as FormFieldTypeId, A as AnyFormField, b as FormSettings, v as FormNotification } from '../types-
|
|
3
|
+
import { q as FormField, s as FormFieldTypeId, A as AnyFormField, b as FormSettings, v as FormNotification } from '../types-DuEFdLNU.js';
|
|
4
4
|
import { FormFieldCatalogType, FieldTypeCatalogEntry } from 'nextly/field-catalog';
|
|
5
5
|
import { ReactNode } from 'react';
|
|
6
6
|
import 'nextly';
|
package/dist/admin/index.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/evaluate-conditions.ts","../src/utils/export-formats.ts","../src/types.ts","../src/config/defaults.ts","../src/utils/form-settings.ts"],"sourcesContent":["/**\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 * Exported for consumers that gate on one condition rather than a full\n * conditional-logic block — notification send-conditions use exactly this.\n *\n * @param condition - The condition to evaluate\n * @param formData - The current form data\n * @returns Whether the condition is met\n */\nexport function 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 * 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, AnyFormField } 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(\n value: unknown,\n field?: AnyFormField\n): 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 // CSV injection: spreadsheets execute cells starting with a formula\n // trigger character, and syntax quoting does NOT stop that. A leading\n // apostrophe forces text interpretation (the OWASP mitigation). Plain\n // negative numbers are exempt — they are data, not formulas.\n const isFormulaLike =\n /^[=+\\-@\\t\\r]/.test(value) && !/^-?\\d+(\\.\\d+)?$/.test(value);\n const escapedFormula = isFormulaLike ? `'${value}` : value;\n\n // Check if quoting is needed\n const needsQuoting =\n escapedFormula.includes(delimiter) ||\n escapedFormula.includes('\"') ||\n escapedFormula.includes(\"\\n\") ||\n escapedFormula.includes(\"\\r\");\n\n if (!needsQuoting) {\n return escapedFormula;\n }\n\n // Escape double quotes by doubling them\n const escaped = escapedFormula.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 // The name ends up inside a Content-Disposition header — restrict it to\n // characters that can never break the header value or the filesystem.\n const safeSlug =\n formSlug.toLowerCase().replace(/[^a-z0-9_-]+/g, \"-\") || \"form\";\n const date = new Date().toISOString().split(\"T\")[0];\n return `${safeSlug}-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 * 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 * @experimental A resolved outgoing form notification (template-based) as built\n * by the submission handler, before it is sent. This is the value transformed by\n * the `form-builder.beforeEmail` D63 filter seam.\n */\nexport interface FormEmailNotification {\n /** Resolved recipient address. */\n to: string;\n /** Email template slug to render. */\n templateSlug: string;\n /** Template variables (submitted data + form metadata). */\n variables: Record<string, unknown>;\n /** Email provider id; undefined = system default. */\n providerId?: string;\n /**\n * Resolved sender address (rule's senderEmail, else the plugin's\n * defaultFrom); undefined = template/provider default.\n */\n from?: string;\n /** Resolved Reply-To address, if the rule configured one. */\n replyTo?: string;\n /** CC addresses, if any. */\n cc?: string[];\n /** BCC addresses, if any. */\n bcc?: string[];\n /** Source notification id; undefined for filter-injected emails. */\n notificationId?: string;\n}\n\n/** @experimental Context passed to the `form-builder.beforeEmail` filter seam. */\nexport interface BeforeEmailFilterContext {\n form: Record<string, unknown>;\n submission: Record<string, unknown>;\n}\n\n/**\n * A single email notification rule stored on a form.\n * Matches the shape saved by the form builder admin UI. This is the ONE\n * notification rule type — the admin context re-exports it rather than\n * keeping a parallel copy.\n */\nexport interface FormNotification {\n /** Unique ID for this notification */\n id: string;\n /** Display name for this notification rule */\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 /**\n * Sender email override. Resolution order at send time:\n * this value, else the plugin's `notifications.defaultFrom` option, else\n * the template/provider default.\n */\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 /**\n * Reply-To address: a `{{fieldName}}` reference to one of the form's\n * email fields (resolved against the submission) or a literal address.\n */\n replyTo?: string;\n /**\n * Optional send condition evaluated against the submitted data. When set\n * and unmet, the rule is skipped for that submission. A single condition\n * covers the common case; the JSON storage leaves room to grow into a\n * multi-condition tree without a migration.\n */\n condition?: ConditionalLogicCondition;\n /** Slug of the email template to use */\n templateSlug?: string;\n}\n\n/**\n * @deprecated Use {@link FormNotification}. Alias kept so existing imports\n * keep compiling during the alpha; will be removed before a stable release.\n */\nexport type FormNotificationItem = FormNotification;\n\n/**\n * Form document structure (stored in database).\n */\nexport interface FormDocument {\n id: string;\n slug: string;\n name: string;\n fields: AnyFormField[];\n settings: FormSettings;\n notifications: FormNotification[];\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 /**\n * Where the submission sits in review.\n *\n * `spam` is one of them, not a separate flag: the stored field offers it, the\n * admin has a Spam tab and filters the other views with `not_equals: \"spam\"`,\n * the notification hook skips it, and marking something \"Not spam\" moves it\n * back to `new`. `spamReason` records WHICH check flagged it.\n */\n status: \"new\" | \"read\" | \"archived\" | \"spam\";\n ipAddress?: string;\n userAgent?: string;\n /** Which spam check flagged this, when one did. */\n spamReason?: string | null;\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 * 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 * Runs as the `form-builder.beforeEmail` D63 filter: the resolved outgoing\n * notifications (template + recipient + variables) are threaded through this\n * callback before they are sent. Allows modifying, adding, or filtering the\n * outgoing notifications. Return the modified array.\n *\n * @example\n * ```typescript\n * beforeEmail: async ({ emails, form, submission }) => {\n * // Add BCC to all outgoing notifications\n * return emails.map((e) => ({ ...e, bcc: ['archive@example.com'] }));\n * }\n * ```\n */\n beforeEmail?: (args: {\n emails: FormEmailNotification[];\n form: FormDocument;\n submission: SubmissionDocument;\n }) => Promise<FormEmailNotification[]> | FormEmailNotification[];\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: AnyFormField[];\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 * A form field of a built-in type OR a plugin-contributed type. Stored form\n * data and the builder work with this; `isKnownFormField` narrows it back to\n * the closed `FormField` union so `switch (field.type)` keeps discriminating\n * the built-ins (a `CustomFormField` in the union itself would collapse that).\n */\nexport type AnyFormField = FormField | CustomFormField;\n\n/**\n * All available built-in 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 * A form field's type on the wire: a built-in or a plugin-contributed type id\n * that opted into the forms surface. The `(string & {})` arm admits the plugin\n * id while keeping built-in autocomplete. Built-in `case` narrowing still works\n * because every field-type switch keeps a `default` for the custom arm.\n */\nexport type FormFieldTypeId = FormFieldType | (string & {});\n\n/** Every built-in form field type, as a value list (the single source shared\n * by the field-type guard and the config validator). */\nexport const BUILT_IN_FORM_FIELD_TYPES: readonly 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 BUILT_IN_FORM_FIELD_TYPES_SET = new Set<string>(\n BUILT_IN_FORM_FIELD_TYPES\n);\n\n/**\n * Narrow an `AnyFormField` to the closed built-in `FormField` union. Returns\n * `false` for a plugin-contributed field, so callers handle it generically\n * before switching on the built-in discriminant.\n */\nexport function isKnownFormField(field: AnyFormField): field is FormField {\n return BUILT_IN_FORM_FIELD_TYPES_SET.has(field.type);\n}\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 * A plugin-contributed form field type (outside the built-in union). Carries\n * only the base properties — the plugin owns its editor component and value\n * semantics — and its `type` is the plugin's registered field-type id. Kept a\n * distinct union member so built-in `case` narrowing is unaffected while a\n * plugin field still type-checks as a `FormField`.\n */\nexport interface CustomFormField extends Omit<BaseFormField, \"type\"> {\n type: string & {};\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\n * - \"relationship\": Redirect to a linked document (requires\n * `redirectRelationships` in the plugin options)\n *\n * @default \"message\"\n */\n confirmationType?: \"message\" | \"redirect\" | \"relationship\";\n\n /**\n * Linked document for relationship-based redirects (the collection's\n * `redirectPage` relationship value). Preserved verbatim — resolution\n * happens where the relationship is rendered.\n */\n redirectPage?: unknown;\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 IP (default true) */\n allowMultipleSubmissions?: boolean;\n\n /**\n * Per-form honeypot override. Unset inherits the plugin's\n * `spamProtection.honeypot`; the form wins where set.\n */\n honeypotEnabled?: boolean;\n\n /**\n * Per-form reCAPTCHA override. Unset inherits the plugin's\n * `spamProtection.recaptcha.enabled`; the form wins where set.\n */\n captchaEnabled?: boolean;\n\n /** reCAPTCHA site key (client-facing) when captcha is enabled per-form */\n captchaSiteKey?: string;\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 /**\n * Submission status.\n *\n * `spam` is one of them, not a separate flag: the stored field offers it, the\n * admin has a Spam tab and filters its other views with `not_equals: \"spam\"`,\n * and marking something \"Not spam\" moves it back to `new`.\n */\n status: \"new\" | \"read\" | \"archived\" | \"spam\";\n\n /** Which spam check flagged this, when one did. */\n spamReason?: string | null;\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 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 honeypotEnabled: config.settings?.honeypotEnabled,\n captchaEnabled: config.settings?.captchaEnabled,\n captchaSiteKey: config.settings?.captchaSiteKey,\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 * The ONE reader for stored form settings. Settings were historically\n * written in two shapes (the builder's old context shape and the collection\n * group's declared shape), so every consumer — the builder UI and the\n * submit handler alike — normalizes through here instead of trusting the\n * stored keys.\n */\n\nimport { DEFAULT_FORM_SETTINGS } from \"../config/defaults\";\nimport type { FormSettings } from \"../types\";\n\nexport { DEFAULT_FORM_SETTINGS };\n\nfunction asOptionalBoolean(value: unknown): boolean | undefined {\n return typeof value === \"boolean\" ? value : undefined;\n}\n\nfunction asOptionalString(value: unknown): string | undefined {\n return typeof value === \"string\" && value !== \"\" ? value : undefined;\n}\n\n/**\n * Normalize a stored settings value into the canonical {@link FormSettings}.\n *\n * Legacy keys are migrated on read (no data is lost, nothing is rewritten\n * until the form is next saved):\n * - `confirmationMessage` → `successMessage` + `confirmationType: \"message\"`\n * - nested `captcha: { enabled, siteKey }` → flat `captchaEnabled`/`captchaSiteKey`\n *\n * Keys with no consumer (`showResetButton`, `resetButtonText`,\n * `storeSubmissions`, `submissionLimit`) are dropped: a setting that does\n * nothing must not survive into the UI.\n */\nexport function normalizeFormSettings(raw: unknown): FormSettings {\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) {\n return { ...DEFAULT_FORM_SETTINGS };\n }\n const source = raw as Record<string, unknown>;\n\n const legacyMessage = asOptionalString(source.confirmationMessage);\n const legacyCaptcha =\n source.captcha && typeof source.captcha === \"object\"\n ? (source.captcha as { enabled?: unknown; siteKey?: unknown })\n : undefined;\n\n // \"relationship\" is a real stored value (redirect to a linked document) —\n // coercing it to \"message\" would disable the form's redirect on the next\n // save. Only genuinely unknown values fall back to \"message\".\n const confirmationType =\n source.confirmationType === \"redirect\" ||\n source.confirmationType === \"relationship\"\n ? source.confirmationType\n : \"message\";\n\n return {\n submitButtonText:\n asOptionalString(source.submitButtonText) ??\n DEFAULT_FORM_SETTINGS.submitButtonText,\n confirmationType,\n successMessage:\n asOptionalString(source.successMessage) ??\n legacyMessage ??\n DEFAULT_FORM_SETTINGS.successMessage,\n redirectUrl: asOptionalString(source.redirectUrl),\n redirectPage: source.redirectPage ?? undefined,\n redirectRelation:\n source.redirectRelation && typeof source.redirectRelation === \"object\"\n ? (source.redirectRelation as FormSettings[\"redirectRelation\"])\n : undefined,\n allowMultipleSubmissions:\n asOptionalBoolean(source.allowMultipleSubmissions) ??\n DEFAULT_FORM_SETTINGS.allowMultipleSubmissions,\n honeypotEnabled: asOptionalBoolean(source.honeypotEnabled),\n captchaEnabled:\n asOptionalBoolean(source.captchaEnabled) ??\n asOptionalBoolean(legacyCaptcha?.enabled),\n captchaSiteKey:\n asOptionalString(source.captchaSiteKey) ??\n asOptionalString(legacyCaptcha?.siteKey),\n };\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as nextly from 'nextly';
|
|
2
2
|
import { CollectionConfig, PluginContext } from 'nextly';
|
|
3
3
|
import { PluginDefinition } from '@nextlyhq/plugin-sdk';
|
|
4
|
-
import { R as ResolvedFormBuilderConfig, F as FormBuilderPluginOptions, a as FormConfig, b as FormSettings, c as FormNotifications, C as CheckboxFormField, D as DateFormField, E as EmailFormField, d as FileFormField, H as HiddenFormField, N as NumberFormField, P as PhoneFormField, e as RadioFormField, S as SelectFormField, T as TextFormField, f as TextareaFormField, g as TimeFormField, U as UrlFormField, h as ConditionalLogic, A as AnyFormField, i as SubmissionDocument, j as FormDocument, W as WebhookEvent } from './types-
|
|
5
|
-
export { B as BUILT_IN_FORM_FIELD_TYPES, k as BaseFormField, l as ConditionalLogicCondition, m as CustomFormField, n as EmailConfig, o as FieldBlockConfig, p as FormAccess, q as FormField, r as FormFieldType, s as FormFieldTypeId, t as FormHookContext, u as FormHooks, v as FormNotification, w as FormNotificationItem, x as FormSubmission, V as ValidationRules, y as WebhookConfig, z as isCheckboxFormField, G as isEmailFormField, I as isFileFormField, J as isFormField, K as isFormFieldType, L as isKnownFormField, M as isSelectFormField, O as isTextFormField } from './types-
|
|
4
|
+
import { R as ResolvedFormBuilderConfig, F as FormBuilderPluginOptions, a as FormConfig, b as FormSettings, c as FormNotifications, C as CheckboxFormField, D as DateFormField, E as EmailFormField, d as FileFormField, H as HiddenFormField, N as NumberFormField, P as PhoneFormField, e as RadioFormField, S as SelectFormField, T as TextFormField, f as TextareaFormField, g as TimeFormField, U as UrlFormField, h as ConditionalLogic, A as AnyFormField, i as SubmissionDocument, j as FormDocument, W as WebhookEvent } from './types-DuEFdLNU.js';
|
|
5
|
+
export { B as BUILT_IN_FORM_FIELD_TYPES, k as BaseFormField, l as ConditionalLogicCondition, m as CustomFormField, n as EmailConfig, o as FieldBlockConfig, p as FormAccess, q as FormField, r as FormFieldType, s as FormFieldTypeId, t as FormHookContext, u as FormHooks, v as FormNotification, w as FormNotificationItem, x as FormSubmission, V as ValidationRules, y as WebhookConfig, z as isCheckboxFormField, G as isEmailFormField, I as isFileFormField, J as isFormField, K as isFormFieldType, L as isKnownFormField, M as isSelectFormField, O as isTextFormField } from './types-DuEFdLNU.js';
|
|
6
6
|
import { z } from 'zod';
|
|
7
7
|
import 'react';
|
|
8
8
|
|
package/dist/index.js
CHANGED
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
normalizeFormSettings,
|
|
27
27
|
pluralize,
|
|
28
28
|
toTitleCase
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-XCICDELK.js";
|
|
30
30
|
|
|
31
31
|
// src/plugin.ts
|
|
32
32
|
import { createRequire } from "module";
|
|
@@ -510,6 +510,17 @@ function submissionsCollection(pluginConfig) {
|
|
|
510
510
|
};
|
|
511
511
|
}
|
|
512
512
|
|
|
513
|
+
// src/document-shapes.ts
|
|
514
|
+
function asSubmissionDocument(row) {
|
|
515
|
+
return row;
|
|
516
|
+
}
|
|
517
|
+
function asSubmissionDocuments(rows) {
|
|
518
|
+
return rows;
|
|
519
|
+
}
|
|
520
|
+
function asFormDocument(row) {
|
|
521
|
+
return row;
|
|
522
|
+
}
|
|
523
|
+
|
|
513
524
|
// src/plugin.ts
|
|
514
525
|
var { version: PLUGIN_VERSION } = createRequire(import.meta.url)(
|
|
515
526
|
"../package.json"
|
|
@@ -694,8 +705,8 @@ function formBuilder(options = {}) {
|
|
|
694
705
|
return Response.json({ items }, { headers: truncationHeaders });
|
|
695
706
|
}
|
|
696
707
|
const csv = exportToCSV(
|
|
697
|
-
items,
|
|
698
|
-
form
|
|
708
|
+
asSubmissionDocuments(items),
|
|
709
|
+
asFormDocument(form)
|
|
699
710
|
);
|
|
700
711
|
const filename = generateExportFilename(
|
|
701
712
|
typeof form.slug === "string" ? form.slug : "form",
|
|
@@ -780,8 +791,8 @@ function formBuilder(options = {}) {
|
|
|
780
791
|
(emails, ctx) => resolvedConfig.beforeEmail({
|
|
781
792
|
emails,
|
|
782
793
|
// Boundary: loose runtime documents → the user's typed contract.
|
|
783
|
-
form: ctx.form,
|
|
784
|
-
submission: ctx.submission
|
|
794
|
+
form: asFormDocument(ctx.form),
|
|
795
|
+
submission: asSubmissionDocument(ctx.submission)
|
|
785
796
|
})
|
|
786
797
|
);
|
|
787
798
|
}
|
|
@@ -2021,12 +2032,11 @@ async function submitForm(options, context) {
|
|
|
2021
2032
|
}
|
|
2022
2033
|
return {
|
|
2023
2034
|
success: true,
|
|
2024
|
-
// The created ROW, not the envelope around it.
|
|
2025
|
-
//
|
|
2026
|
-
//
|
|
2027
|
-
//
|
|
2028
|
-
|
|
2029
|
-
submission: submission.item,
|
|
2035
|
+
// The created ROW, not the envelope around it. Through the shared
|
|
2036
|
+
// conversion, which is where the unchecked step lives and says so; the
|
|
2037
|
+
// integration test reads `submission.id` off the result because nothing
|
|
2038
|
+
// at compile time will.
|
|
2039
|
+
submission: asSubmissionDocument(submission.item),
|
|
2030
2040
|
redirect
|
|
2031
2041
|
};
|
|
2032
2042
|
} catch (error) {
|
|
@@ -2049,7 +2059,7 @@ async function fetchFormBySlug(slug, pluginConfig, pluginContext) {
|
|
|
2049
2059
|
{ as: "system" }
|
|
2050
2060
|
);
|
|
2051
2061
|
const form = result.data?.[0];
|
|
2052
|
-
return form ? form : null;
|
|
2062
|
+
return form ? asFormDocument(form) : null;
|
|
2053
2063
|
} catch {
|
|
2054
2064
|
return null;
|
|
2055
2065
|
}
|