@10x-media/form-builder 0.1.0-beta.26 → 0.1.0-beta.27

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 (81) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/LICENSE +21 -21
  3. package/dist/actions/dispatch.js +1 -1
  4. package/dist/actions/task.js +4 -4
  5. package/dist/actions/task.js.map +1 -1
  6. package/dist/aggregation/aggregateResponses.js +3 -4
  7. package/dist/aggregation/aggregateResponses.js.map +1 -1
  8. package/dist/aggregation/resolveResultsRequest.js +2 -2
  9. package/dist/collections/formSubmissions.js +11 -12
  10. package/dist/collections/formSubmissions.js.map +1 -1
  11. package/dist/collections/forms.js +12 -12
  12. package/dist/collections/forms.js.map +1 -1
  13. package/dist/consent/resolveConsentSourcesRequest.js +2 -2
  14. package/dist/consent/resolveConsentSourcesRequest.js.map +1 -1
  15. package/dist/exports/react.d.ts +2 -2
  16. package/dist/exports/react.js +2 -2
  17. package/dist/exports/rsc.d.ts +2 -1
  18. package/dist/exports/rsc.js +3 -2
  19. package/dist/exports/types.d.ts +2 -1
  20. package/dist/form/findFormAtLocale.js +2 -2
  21. package/dist/form/findFormAtLocale.js.map +1 -1
  22. package/dist/index.d.ts +2 -1
  23. package/dist/index.js +14 -9
  24. package/dist/index.js.map +1 -1
  25. package/dist/options.d.ts +15 -1
  26. package/dist/plugin/collectionSlugs.d.ts +28 -0
  27. package/dist/plugin/collectionSlugs.js +65 -0
  28. package/dist/plugin/collectionSlugs.js.map +1 -0
  29. package/dist/plugin/customState.js +1 -1
  30. package/dist/plugin/customState.js.map +1 -1
  31. package/dist/plugin/registerCollections.js +7 -2
  32. package/dist/plugin/registerCollections.js.map +1 -1
  33. package/dist/poll/closeJob.js +2 -2
  34. package/dist/poll/closeJob.js.map +1 -1
  35. package/dist/poll/outcomeBeforeChange.js +2 -2
  36. package/dist/poll/outcomeBeforeChange.js.map +1 -1
  37. package/dist/poll/resolvePollCloseRequest.js +3 -3
  38. package/dist/poll/resolvePollCloseRequest.js.map +1 -1
  39. package/dist/poll/resolvePollOptionsRequest.js +2 -2
  40. package/dist/poll/resolvePollOptionsRequest.js.map +1 -1
  41. package/dist/poll/resolvePollOutcome.js +3 -3
  42. package/dist/poll/resolvePollOutcome.js.map +1 -1
  43. package/dist/poll/votes/aggregateFromVotes.js +3 -2
  44. package/dist/poll/votes/aggregateFromVotes.js.map +1 -1
  45. package/dist/poll/votes/bumpPollVote.js +9 -6
  46. package/dist/poll/votes/bumpPollVote.js.map +1 -1
  47. package/dist/poll/votes/recountPollVotes.js +4 -4
  48. package/dist/poll/votes/recountPollVotes.js.map +1 -1
  49. package/dist/poll/votes/voteTallyHook.js +2 -2
  50. package/dist/poll/votes/voteTallyHook.js.map +1 -1
  51. package/dist/poll/votes/votesCollection.d.ts +5 -1
  52. package/dist/poll/votes/votesCollection.js +7 -2
  53. package/dist/poll/votes/votesCollection.js.map +1 -1
  54. package/dist/react/Form.d.ts +11 -2
  55. package/dist/react/Form.js +15 -8
  56. package/dist/react/Form.js.map +1 -1
  57. package/dist/react/Poll.d.ts +1 -1
  58. package/dist/react/Poll.js +3 -0
  59. package/dist/react/Poll.js.map +1 -1
  60. package/dist/react/fetchResults.d.ts +5 -3
  61. package/dist/react/fetchResults.js +6 -4
  62. package/dist/react/fetchResults.js.map +1 -1
  63. package/dist/react/submitForm.d.ts +8 -3
  64. package/dist/react/submitForm.js +5 -3
  65. package/dist/react/submitForm.js.map +1 -1
  66. package/dist/submissions/createSubmission.d.ts +1 -1
  67. package/dist/submissions/createSubmission.js +3 -3
  68. package/dist/submissions/createSubmission.js.map +1 -1
  69. package/dist/submissions/resolveVotedSubmission.js +5 -5
  70. package/dist/submissions/resolveVotedSubmission.js.map +1 -1
  71. package/dist/submissions/runSubmission.js +1 -1
  72. package/dist/submissions/validateSubmission.js +7 -7
  73. package/dist/submissions/validateSubmission.js.map +1 -1
  74. package/dist/submissions/voteChangeEndpoint.js +11 -10
  75. package/dist/submissions/voteChangeEndpoint.js.map +1 -1
  76. package/dist/submissions/votedCookie.d.ts +8 -4
  77. package/dist/submissions/votedCookie.js +18 -15
  78. package/dist/submissions/votedCookie.js.map +1 -1
  79. package/dist/validation/builtin/notAlreadySubmitted.js +2 -2
  80. package/dist/validation/builtin/notAlreadySubmitted.js.map +1 -1
  81. package/package.json +7 -5
@@ -1 +1 @@
1
- {"version":3,"file":"forms.js","names":[],"sources":["../../src/collections/forms.ts"],"sourcesContent":["import {\n\ttype CollectionAfterChangeHook,\n\ttype CollectionAfterReadHook,\n\ttype CollectionBeforeValidateHook,\n\ttype CollectionConfig,\n\ttype CollectionSlug,\n\ttype Field,\n\ttype PayloadRequest,\n\ttype TextFieldSingleValidation,\n\tValidationError,\n} from 'payload'\nimport type { RichTextBodyOption } from '../actions/body/serializeBody'\nimport { buildActionBlocks } from '../actions/buildActionBlocks'\nimport type { FromAddressesResolver, FromAddressSourceRegistry } from '../actions/fromAddresses'\nimport type { ActionRegistry } from '../actions/registry'\nimport type { ActionInstance } from '../actions/runActions'\nimport type { FormResultsAccess } from '../aggregation/resolveResultsRequest'\nimport { type CalcAllowed, normalizeCalc } from '../calc/normalizeCalc'\nimport type { CalcSource } from '../calc/registry'\nimport { calcUsesSources, resolveCalcContext } from '../calc/resolveCalcContext'\nimport { buildConditionTypeMap } from '../conditions/conditionType'\nimport {\n\tbuildOperandTypes,\n\ttype FieldRow,\n\tnormalizeFormConditions,\n\tnormalizeWhere,\n} from '../conditions/normalizeConditions'\nimport { resolveConsentStatements } from '../consent/resolveConsentStatements'\nimport type { ConsentSourcesResolver } from '../consent/types'\nimport type { DepartmentEmailsResolver } from '../email/departments'\nimport { buildFieldBlocks } from '../fields/buildFieldBlocks'\nimport { fieldNamesOfType } from '../fields/fieldNamesOfType'\nimport { localizedIf } from '../fields/localizedIf'\nimport type { FieldTypeRegistry } from '../fields/registry'\nimport { normalizeFlow } from '../flow/normalizeFlow'\nimport { END_OF_FORM } from '../flow/types'\nimport { pollConfigOf } from '../form/pollState'\nimport { isLoggedIn } from '../plugin/access'\nimport type { CollectionOverrides } from '../plugin/collectionOverrides'\nimport { buildPollOptionSourceFields } from '../poll/buildPollOptionSourceFields'\nimport { enqueuePollClose } from '../poll/closeJob'\nimport { pollOutcomeBeforeChange } from '../poll/outcomeBeforeChange'\nimport { buildDefaultOutcomeFields, type OutcomeFieldsOverride } from '../poll/outcomeFields'\nimport { type PollTypeRegistry, resolvePollTypes } from '../poll/pollTypeRegistry'\nimport type { PollOptionSourceRegistry } from '../poll/registry'\nimport { buildValidateResultsField, pollEligibleTypes } from '../poll/resultsField'\nimport type { FormFieldInstance } from '../submissions/types'\nimport { keys } from '../translations/keys'\nimport { asTranslate, labelForKey, resolveDefinitionLabel } from '../translations/server'\nimport type { ValidationRuleRegistry } from '../validation/registry'\nimport { validateUrl } from '../validation/validateUrl'\nimport { type ButtonsOption, buildDefaultButtonFields } from './buttonFields'\nimport { buildFormsEndpoints } from './formsEndpoints'\nimport type { ResponseOption } from './redirectFields'\nimport { composeSettingsFields, type SettingsOption } from './settingsFields'\n\nexport const FORMS_SLUG = 'forms'\n\n/** `req.context` key under which `consentAfterRead` tracks the form ids it is currently resolving, to break re-entrant reads. */\nconst CONSENT_AFTER_READ_GUARD = 'formBuilderConsentAfterReadInFlight'\n\n/** `req.context` key under which `calcAfterRead` tracks the form ids it is currently resolving, to break re-entrant reads. */\nconst CALC_AFTER_READ_GUARD = 'formBuilderCalcAfterReadInFlight'\n\n/**\n * Require the title in the default locale (and on hosts without localization), but let it be empty in\n * other locales so a localized form falls back to the default-locale title rather than forcing a\n * translation for every locale. Payload's field-level `required` cannot express this: it enforces per\n * write-locale, which would make a title mandatory in every locale and break the documented fallback.\n */\nconst validateFormTitle: TextFieldSingleValidation = (value, { req }) => {\n\tconst localization = req.payload.config.localization\n\tconst defaultLocale = localization ? localization.defaultLocale : undefined\n\tconst enforced =\n\t\tdefaultLocale === undefined || req.locale === undefined || req.locale === defaultLocale\n\tif (enforced && (typeof value !== 'string' || value.trim().length === 0)) {\n\t\treturn req.t('validation:required')\n\t}\n\treturn true\n}\n\n/**\n * Field-level validation for the raw flow JSON: step ids must be non-empty, unique, and not the\n * reserved `END_OF_FORM` sentinel, and every explicit step-id reference (a string `next`, a\n * transition `to`) must resolve to a known step. `next: null` (explicit end of form) and an\n * absent `next` (fall through to the next step in array order) are always valid. On full-document\n * saves the beforeValidate pass has already run `normalizeFlow` and laundered dangling\n * references, so the unknown-target checks mainly protect partial API updates that send `flow`\n * without `fields`.\n */\nconst validateFlow = (\n\traw: unknown,\n\t{ req }: { data?: unknown; req: PayloadRequest }\n): string | true => {\n\tif (raw === null || raw === undefined) return true\n\tconst r = raw as Record<string, unknown>\n\tif (!Array.isArray(r.steps)) return true\n\tconst steps = r.steps as Array<Record<string, unknown>>\n\tconst t = asTranslate(req.t)\n\tconst emptyIdStep = steps.find((s) => typeof s?.id !== 'string' || s.id.length === 0)\n\tif (emptyIdStep) return t(keys.flowStepIdEmpty)\n\tif (steps.some((s) => s.id === END_OF_FORM)) {\n\t\treturn t(keys.flowStepIdReserved).replace('{id}', END_OF_FORM)\n\t}\n\tconst ids = steps.map((s) => s.id as string)\n\tif (new Set(ids).size !== ids.length) {\n\t\treturn t(keys.flowDuplicateStepIds)\n\t}\n\tconst idSet = new Set(ids)\n\tfor (const step of steps) {\n\t\tconst id = step.id as string\n\t\tif (typeof step.next === 'string' && step.next.length > 0 && !idSet.has(step.next)) {\n\t\t\treturn t(keys.flowUnknownNext).replace('{id}', id).replace('{next}', step.next)\n\t\t}\n\t\tif (Array.isArray(step.transitions)) {\n\t\t\tfor (const trans of step.transitions as Array<Record<string, unknown>>) {\n\t\t\t\tif (typeof trans?.to === 'string' && trans.to.length > 0 && !idSet.has(trans.to)) {\n\t\t\t\t\treturn t(keys.flowUnknownTransition).replace('{id}', id).replace('{to}', trans.to)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn true\n}\n\n/** How many steps the caller actually submitted, before normalization strips/collapses the flow. */\nconst providedFlowStepCount = (raw: unknown): number => {\n\tif (raw === null || typeof raw !== 'object') return 0\n\tconst steps = (raw as { steps?: unknown }).steps\n\treturn Array.isArray(steps) ? steps.length : 0\n}\n\n/**\n * Stamp the configured uploads collection slug onto every `file` block (including repeater\n * sub-fields) on each save. The block carries a hidden `uploadsCollection` field so the slug\n * reaches the client renderer through the form document; the server always overwrites it from\n * plugin config, so the stored value is never author- or client-controlled.\n */\nconst stampFileCollections = (rows: FieldRow[], slug: string): void => {\n\tfor (const row of rows) {\n\t\tif (row.blockType === 'file') {\n\t\t\t;(row as { uploadsCollection?: string }).uploadsCollection = slug\n\t\t}\n\t\tconst subFields = (row as { subFields?: unknown }).subFields\n\t\tif (Array.isArray(subFields)) {\n\t\t\tstampFileCollections(subFields as FieldRow[], slug)\n\t\t}\n\t}\n}\n\ntype BuildFormsCollectionArgs = {\n\tregistry: FieldTypeRegistry\n\truleRegistry: ValidationRuleRegistry\n\t/** Registered calc extension names; expressions referencing anything else are dropped on save. */\n\tcalcAllowed?: CalcAllowed\n\t/** Registered calc sources; when a form's expressions use any, an afterRead hook stamps `doc.calcResolved`. */\n\tcalcSources?: Record<string, CalcSource>\n\t/**\n\t * The plugin `consent.sources` option. Present: the `/:id/consent-sources` endpoint backing the\n\t * consent field's source select is registered. Absent: neither it nor the consent field type exists.\n\t */\n\tconsentSources?: ConsentSourcesResolver\n\t/** Plugin `consent.resolveOnRead` (default true); `false` skips the per-read consent afterRead hook. */\n\tconsentResolveOnRead?: boolean\n\tactionRegistry?: ActionRegistry\n\tlocalizeContent?: boolean\n\t/** The plugin `richText` option; `responseEditor ?? editor` sets the response message field's editor. */\n\trichText?: RichTextBodyOption\n\t/** The host-owned uploads collection slug from plugin config; absent when uploads are disabled. */\n\tuploadsCollectionSlug?: string\n\t/** Host seam gating anonymous results reads (plugin option `results.access`). */\n\tresultsAccess?: FormResultsAccess\n\t/**\n\t * Whether the hidden tally store is registered (`poll.votes !== false`). Threads into the poll\n\t * endpoints (tally-backed reads) and, when false, forbids poll forms from disabling\n\t * `persistSubmissions`: with neither store nor submissions there would be nothing to count.\n\t */\n\tpollVotesEnabled?: boolean\n\t/** Registered poll option sources (plugin option `poll.sources`); empty registry means no source fields. */\n\tpollSourceRegistry?: PollOptionSourceRegistry\n\t/** Registered poll outcome strategies (`poll.types`); drives the poll `type` select options. Defaults to the built-ins. */\n\tpollTypeRegistry?: PollTypeRegistry\n\t/** The plugin `poll.outcomeFields` seam; composes the outcome group from the two default fields. */\n\toutcomeFields?: OutcomeFieldsOverride\n\t/** The plugin `buttons` option; `fields` composes the `{ submit, prev, next }` labels from the localized defaults. */\n\tbuttons?: ButtonsOption\n\t/** The plugin `settings` option; `fields` composes the sidebar flag checkboxes from the defaults. */\n\tsettings?: SettingsOption\n\t/** The plugin `response` option; `redirect.fields` composes the `response.redirect` group from its default fields. */\n\tresponse?: ResponseOption\n\t/**\n\t * The plugin `email.fromAddresses` option. Either this or `fromSources` present: both email\n\t * actions gain a `from` select and the `/:id/from-addresses` endpoint is registered. Both\n\t * absent: neither exists.\n\t */\n\tfromAddresses?: FromAddressesResolver\n\t/** The plugin `email.fromSources` option: send-time-resolved senders served ahead of the literals. */\n\tfromSources?: FromAddressSourceRegistry\n\t/**\n\t * The plugin `email.departments` option. Present: the `emailTeam` `to` becomes a department select\n\t * and the `/:id/departments` endpoint backing it is registered. Absent: `to` stays a plain field.\n\t */\n\tdepartments?: DepartmentEmailsResolver\n\t/**\n\t * The plugin `redirectRelationships` option. Non-empty: `response.redirect` gains a polymorphic\n\t * `reference` relationship field so an author can redirect to an internal document instead of\n\t * (or alongside) a URL. Absent or empty: no `reference` field exists, matching today's URL-only\n\t * redirect.\n\t */\n\tredirectRelationships?: CollectionSlug[]\n\toverrides?: CollectionOverrides\n}\n\nconst isPlainObject = (value: unknown): value is Record<string, unknown> => {\n\tif (value == null || typeof value !== 'object') {\n\t\treturn false\n\t}\n\tconst proto = Object.getPrototypeOf(value)\n\treturn proto === Object.prototype || proto === null\n}\n\n/**\n * Mirror how Payload persists a partial update: plain objects merge recursively (an unsent group\n * sibling keeps its stored value), arrays and everything else are replaced by the delta, and an\n * explicit `null` survives as null. A shallow spread would hand `validateConfig` a group missing\n * the siblings the update did not send.\n */\nconst mergeSavedShape = (\n\toriginal: Record<string, unknown>,\n\tdelta: Record<string, unknown>\n): Record<string, unknown> => {\n\tconst merged: Record<string, unknown> = { ...original }\n\tfor (const [key, value] of Object.entries(delta)) {\n\t\tconst base = merged[key]\n\t\tmerged[key] = isPlainObject(base) && isPlainObject(value) ? mergeSavedShape(base, value) : value\n\t}\n\treturn merged\n}\n\nexport const buildFormsCollection = ({\n\toverrides,\n\tregistry,\n\truleRegistry,\n\tcalcAllowed,\n\tcalcSources,\n\tconsentSources,\n\tconsentResolveOnRead,\n\tactionRegistry = new Map(),\n\tlocalizeContent = true,\n\trichText,\n\tuploadsCollectionSlug,\n\tresultsAccess,\n\tpollVotesEnabled = false,\n\tpollSourceRegistry,\n\tpollTypeRegistry,\n\toutcomeFields,\n\tbuttons,\n\tsettings,\n\tresponse,\n\tfromAddresses,\n\tfromSources,\n\tdepartments,\n\tredirectRelationships,\n}: BuildFormsCollectionArgs): CollectionConfig => {\n\tconst conditionTypes = buildConditionTypeMap(registry)\n\tconst pollTypes = pollTypeRegistry ?? resolvePollTypes()\n\tconst pollResultsTypes = pollEligibleTypes(registry)\n\tconst bareTypes = new Set(\n\t\t[...registry.values()].filter((d) => d.bare === true).map((d) => d.type)\n\t)\n\t// Handed to the FlowBuilder so it can list bare (nameless) blocks in the step picker: slug ->\n\t// label (an i18n key or literal, resolved client-side).\n\tconst bareTypeLabels = Object.fromEntries(\n\t\t[...registry.values()].filter((d) => d.bare === true).map((d) => [d.type, d.label])\n\t)\n\t// The success `response` message field's editor: `richText.responseEditor` overrides the plugin-wide\n\t// `richText.editor` for this field alone (mirroring `bodyEditor` for action bodies).\n\tconst responseEditor = richText?.responseEditor ?? richText?.editor\n\tconst FLOW_BUILDER_REF = '@10x-media/form-builder/client#FlowBuilder'\n\tconst FIELD_NAME_SELECT_REF = '@10x-media/form-builder/client#FieldNameSelect'\n\tconst FLOW_STEPS_CELL_REF = '@10x-media/form-builder/client#FlowStepsCell'\n\tconst FIELD_COUNT_CELL_REF = '@10x-media/form-builder/client#FieldCountCell'\n\tconst CLOSE_POLL_BUTTON_REF = '@10x-media/form-builder/client#ClosePollButton'\n\t// The `source` outcome strategy delegates to the poll's option source, so it is useless without one:\n\t// offer it in the author-facing select only when option sources are registered. The strategy stays in\n\t// the registry regardless, so `resolvePollOutcome` and host code can still address it.\n\tconst hasOptionSources = (pollSourceRegistry?.size ?? 0) > 0\n\t// `mostVoted` leads the author-facing select because it is the default: an enabled poll works\n\t// immediately, with no winner to hand-pick. Only the select order changes; the registry keeps its\n\t// own order (host strategies stay after the built-ins).\n\tconst orderedPollTypes = [\n\t\t...[...pollTypes.values()].filter((strategy) => strategy.type === 'mostVoted'),\n\t\t...[...pollTypes.values()].filter((strategy) => strategy.type !== 'mostVoted'),\n\t]\n\n\tconst beforeValidate: CollectionBeforeValidateHook = ({ data, originalDoc, req }) => {\n\t\t// Without the tally store, pruned submissions would erase every vote: a poll form may only\n\t\t// turn `persistSubmissions` off while the store is registered to carry the counts. Evaluated\n\t\t// on the incoming partial merged over the stored doc (outcomeBeforeChange precedent), so a\n\t\t// PATCH flipping either flag alone cannot slip past the guard.\n\t\tif (!pollVotesEnabled) {\n\t\t\tconst pollOn = (data?.pollEnabled ?? originalDoc?.pollEnabled) === true\n\t\t\tconst persistOff = (data?.persistSubmissions ?? originalDoc?.persistSubmissions) === false\n\t\t\tif (pollOn && persistOff) {\n\t\t\t\tthrow new ValidationError(\n\t\t\t\t\t{\n\t\t\t\t\t\tcollection: FORMS_SLUG,\n\t\t\t\t\t\terrors: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tpath: 'persistSubmissions',\n\t\t\t\t\t\t\t\tmessage: asTranslate(req.t)(keys.pollNeedsPersistedSubmissions),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t},\n\t\t\t\t\treq.t\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\t{\n\t\t\t// Changeable votes update the voter's stored submission in place, so `allowChange` cannot\n\t\t\t// coexist with pruning: a pruned row would make the next \"change\" a second, double-counted\n\t\t\t// vote. Same merged-partial evaluation as the guard above.\n\t\t\tconst pollOn = (data?.pollEnabled ?? originalDoc?.pollEnabled) === true\n\t\t\tconst persistOff = (data?.persistSubmissions ?? originalDoc?.persistSubmissions) === false\n\t\t\tconst allowChangeOn =\n\t\t\t\t(pollConfigOf(data?.poll)?.allowChange ?? pollConfigOf(originalDoc?.poll)?.allowChange) ===\n\t\t\t\ttrue\n\t\t\tif (pollOn && persistOff && allowChangeOn) {\n\t\t\t\tthrow new ValidationError(\n\t\t\t\t\t{\n\t\t\t\t\t\tcollection: FORMS_SLUG,\n\t\t\t\t\t\terrors: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tpath: 'persistSubmissions',\n\t\t\t\t\t\t\t\tmessage: asTranslate(req.t)(keys.pollAllowChangeNeedsPersistedSubmissions),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t},\n\t\t\t\t\treq.t\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\tif (data && Array.isArray(data.fields)) {\n\t\t\tconst normalized: FieldRow[] = normalizeFormConditions(\n\t\t\t\tdata.fields as FieldRow[],\n\t\t\t\tconditionTypes\n\t\t\t)\n\t\t\tfor (const field of normalized) {\n\t\t\t\tif ('expression' in field) {\n\t\t\t\t\tfield.expression = normalizeCalc(field.expression, calcAllowed)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (uploadsCollectionSlug) {\n\t\t\t\tstampFileCollections(normalized, uploadsCollectionSlug)\n\t\t\t}\n\t\t\tdata.fields = normalized\n\t\t\t// Flow step assignments store field keys: machine names for named fields, block row ids\n\t\t\t// for bare (nameless) blocks. Mirrors `fieldKey` over the raw rows.\n\t\t\tconst fieldKeys = normalized\n\t\t\t\t.map((field: FieldRow) => {\n\t\t\t\t\tif (typeof field.name === 'string' && field.name.length > 0) {\n\t\t\t\t\t\treturn field.name\n\t\t\t\t\t}\n\t\t\t\t\treturn bareTypes.has(field.blockType) && field.id != null ? String(field.id) : undefined\n\t\t\t\t})\n\t\t\t\t.filter((key): key is string => key !== undefined)\n\t\t\t// Launder flow transition `when` clauses with the same operand-type rules as field conditions,\n\t\t\t// so a transition referencing a deleted field is dropped, not left as a route that always\n\t\t\t// matches at navigation time and force-routes the visitor.\n\t\t\tconst operandTypes = buildOperandTypes(normalized, conditionTypes)\n\t\t\tconst normalizedFlow = normalizeFlow(data.flow, fieldKeys, (w) =>\n\t\t\t\tnormalizeWhere(w, operandTypes)\n\t\t\t)\n\t\t\t// A flow the author built but that collapses to fewer than two valid steps would\n\t\t\t// otherwise vanish silently. Surface it instead of discarding their work.\n\t\t\tif (providedFlowStepCount(data.flow) > 0 && normalizedFlow === undefined) {\n\t\t\t\tthrow new ValidationError(\n\t\t\t\t\t{\n\t\t\t\t\t\tcollection: FORMS_SLUG,\n\t\t\t\t\t\terrors: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tpath: 'flow',\n\t\t\t\t\t\t\t\tmessage: asTranslate(req.t)(keys.flowNeedsTwoSteps),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t},\n\t\t\t\t\treq.t\n\t\t\t\t)\n\t\t\t}\n\t\t\tdata.flow = normalizedFlow\n\t\t\t// A poll needs a field whose answers are the votes. With the poll enabled and no vote field\n\t\t\t// chosen (and the outcome not coming from an option source), bind one: auto-fill the sole\n\t\t\t// eligible field for a one-choice form, otherwise surface a clear error rather than storing a\n\t\t\t// poll that can never aggregate. The optionSource/`source` exemption preserves domain-driven\n\t\t\t// polls that have no on-form vote field. `buildValidateResultsField` still checks a set value;\n\t\t\t// this is the enforcer for enabled polls.\n\t\t\tif (data.pollEnabled === true) {\n\t\t\t\tconst poll =\n\t\t\t\t\tdata.poll != null && typeof data.poll === 'object'\n\t\t\t\t\t\t? (data.poll as Record<string, unknown>)\n\t\t\t\t\t\t: {}\n\t\t\t\tconst resultsField = typeof poll.resultsField === 'string' ? poll.resultsField.trim() : ''\n\t\t\t\tconst optionSource = typeof poll.optionSource === 'string' ? poll.optionSource.trim() : ''\n\t\t\t\tconst sourceDriven = optionSource.length > 0 || poll.type === 'source'\n\t\t\t\tif (resultsField.length === 0 && !sourceDriven) {\n\t\t\t\t\tconst eligible = fieldNamesOfType(data.fields, pollResultsTypes)\n\t\t\t\t\tif (eligible.length === 1) {\n\t\t\t\t\t\tdata.poll = { ...poll, resultsField: eligible[0] }\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst messageKey =\n\t\t\t\t\t\t\teligible.length > 1 ? keys.pollVoteFieldChoose : keys.pollVoteFieldMissing\n\t\t\t\t\t\tthrow new ValidationError(\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcollection: FORMS_SLUG,\n\t\t\t\t\t\t\t\terrors: [{ path: 'poll.resultsField', message: asTranslate(req.t)(messageKey) }],\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\treq.t\n\t\t\t\t\t\t)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn data\n\t}\n\n\t// Save-time seam for an action's own cross-field checks: each stored instance runs its\n\t// definition's `validateConfig` against the merged form data, and every refusal lands on the\n\t// action block itself (`actions.<index>`), not on whichever config field happens to be required.\n\t// Stored actions are validated even when a partial update does not resend them, because the\n\t// check may span the whole form (an action body referencing a field the update just deleted).\n\tconst actionConfigBeforeValidate: CollectionBeforeValidateHook = async ({\n\t\tdata,\n\t\toriginalDoc,\n\t\treq,\n\t}) => {\n\t\tconst instances = (data?.actions ?? originalDoc?.actions) as ActionInstance[] | undefined\n\t\tif (!Array.isArray(instances) || instances.length === 0) {\n\t\t\treturn data\n\t\t}\n\t\tconst merged = mergeSavedShape(\n\t\t\t(originalDoc ?? {}) as Record<string, unknown>,\n\t\t\t(data ?? {}) as Record<string, unknown>\n\t\t)\n\t\tconst errors: { path: string; message: string }[] = []\n\t\tfor (const [index, instance] of instances.entries()) {\n\t\t\tconst validate = actionRegistry.get(instance.blockType)?.validateConfig\n\t\t\tif (!validate) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tconst verdict = await validate(instance, { data: merged, req })\n\t\t\tif (verdict !== true) {\n\t\t\t\terrors.push({ path: `actions.${index}`, message: verdict })\n\t\t\t}\n\t\t}\n\t\tif (errors.length > 0) {\n\t\t\tthrow new ValidationError({ collection: FORMS_SLUG, errors }, req.t)\n\t\t}\n\t\treturn data\n\t}\n\n\t// After every save, (re)schedule the poll's auto-close job when it applies (enabled + closesAt +\n\t// unresolved + non-manual strategy + a job runner present). Best-effort and non-throwing, so it\n\t// never affects the save; without a runner the resolve-on-read fallback in the results endpoint\n\t// heals the outcome instead.\n\tconst pollCloseAfterChange: CollectionAfterChangeHook = async ({ doc, req }) => {\n\t\tawait enqueuePollClose({ payload: req.payload, form: doc, req })\n\t\treturn doc\n\t}\n\n\tconst fieldsField: Field = {\n\t\tname: 'fields',\n\t\ttype: 'blocks',\n\t\tblocks: buildFieldBlocks({ registry, ruleRegistry, localize: localizeContent }),\n\t\tadmin: { components: { Cell: FIELD_COUNT_CELL_REF } },\n\t}\n\n\tconst flowField: Field = {\n\t\tname: 'flow',\n\t\ttype: 'json',\n\t\tvalidate: validateFlow,\n\t\tadmin: {\n\t\t\tcomponents: {\n\t\t\t\tCell: FLOW_STEPS_CELL_REF,\n\t\t\t\tField: { path: FLOW_BUILDER_REF, clientProps: { conditionTypes, bareTypeLabels } },\n\t\t\t},\n\t\t},\n\t\t// Narrows the generated TypeScript type from opaque JSON to FormFlow so callers\n\t\t// don't need a cast. Keep this in sync with src/flow/types.ts.\n\t\ttypescriptSchema: [\n\t\t\t() => ({\n\t\t\t\ttype: 'object' as const,\n\t\t\t\trequired: ['steps'],\n\t\t\t\tadditionalProperties: false,\n\t\t\t\tproperties: {\n\t\t\t\t\tsteps: {\n\t\t\t\t\t\ttype: 'array' as const,\n\t\t\t\t\t\titems: {\n\t\t\t\t\t\t\ttype: 'object' as const,\n\t\t\t\t\t\t\trequired: ['id'],\n\t\t\t\t\t\t\tadditionalProperties: true,\n\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\tid: { type: 'string' as const },\n\t\t\t\t\t\t\t\ttitle: { type: 'string' as const },\n\t\t\t\t\t\t\t\tfields: { type: 'array' as const, items: { type: 'string' as const } },\n\t\t\t\t\t\t\t\tnext: { type: ['string', 'null'] as ('string' | 'null')[] },\n\t\t\t\t\t\t\t\ttransitions: {\n\t\t\t\t\t\t\t\t\ttype: 'array' as const,\n\t\t\t\t\t\t\t\t\titems: {\n\t\t\t\t\t\t\t\t\t\ttype: 'object' as const,\n\t\t\t\t\t\t\t\t\t\trequired: ['to'],\n\t\t\t\t\t\t\t\t\t\tadditionalProperties: true,\n\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\tto: { type: 'string' as const },\n\t\t\t\t\t\t\t\t\t\t\twhen: { type: 'object' as const, additionalProperties: true },\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t],\n\t}\n\n\tconst actionsField: Field = {\n\t\tname: 'actions',\n\t\ttype: 'blocks',\n\t\tblocks: buildActionBlocks(actionRegistry),\n\t\tlabel: labelForKey(keys.configActions),\n\t\t// Action config can contain secrets (e.g. signedWebhook.secret). The collection\n\t\t// itself is publicly readable so forms can be rendered by anonymous clients, but\n\t\t// action config must never be exposed to anonymous callers.\n\t\taccess: { read: isLoggedIn },\n\t}\n\n\t// Optional polymorphic reference to an internal document, for `response.redirect` to point at\n\t// instead of (or alongside) a URL. Absent unless `redirectRelationships` is a non-empty array:\n\t// always polymorphic even for one slug (the consent `page` field's precedent), so a host adding\n\t// a second collection later never changes the stored shape. The plugin never resolves this to a\n\t// URL itself; `toFormDocument` passes the raw `{ relationTo, value }` through for the host to\n\t// resolve, since only the host knows its own routing.\n\tconst redirectReferenceField: Field[] =\n\t\tredirectRelationships && redirectRelationships.length > 0\n\t\t\t? [\n\t\t\t\t\t{\n\t\t\t\t\t\tname: 'reference',\n\t\t\t\t\t\ttype: 'relationship',\n\t\t\t\t\t\trelationTo: redirectRelationships,\n\t\t\t\t\t\tlabel: labelForKey(keys.responseRedirectReference),\n\t\t\t\t\t\tadmin: { description: labelForKey(keys.responseRedirectReferenceDescription) },\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t: []\n\n\t// The redirect group's fields: the `url` text field plus the optional polymorphic `reference`.\n\t// The `response.redirect.fields` seam composes them (mirroring `buttons.fields`), so a host can\n\t// prepend a custom link field, swap `url` for their own picker, or filter; the default (no\n\t// override) is the same `[url, ...reference]` as before. Built-in redirect handling still reads\n\t// `redirect.url`/`redirect.reference`, so a host replacing `url` owns resolving their field to a\n\t// destination in their frontend.\n\tconst urlField: Field = {\n\t\tname: 'url',\n\t\ttype: 'text',\n\t\tlabel: labelForKey(keys.responseUrl),\n\t\tvalidate: validateUrl,\n\t}\n\tconst defaultRedirectFields: Field[] = [urlField, ...redirectReferenceField]\n\tconst redirectFields = response?.redirect?.fields\n\t\t? response.redirect.fields({ defaultFields: defaultRedirectFields })\n\t\t: defaultRedirectFields\n\n\t// What the visitor sees after a successful submit. Publicly readable (unlike actions): the\n\t// client renderer needs message/redirect. `type`/`url` are behavior, never localized;\n\t// `message` is visitor-facing content and follows `localizeContent`.\n\t// `type` is defaulted and not clearable rather than `required`: a required member would make\n\t// the whole group required in generated types, breaking typed `payload.create` calls that\n\t// omit `response`. Consumers treat a missing type as 'message'.\n\tconst responseField: Field = {\n\t\tname: 'response',\n\t\ttype: 'group',\n\t\tfields: [\n\t\t\t{\n\t\t\t\tname: 'type',\n\t\t\t\ttype: 'select',\n\t\t\t\tdefaultValue: 'message',\n\t\t\t\tlabel: labelForKey(keys.responseType),\n\t\t\t\tadmin: { isClearable: false },\n\t\t\t\toptions: [\n\t\t\t\t\t{ label: labelForKey(keys.responseTypeMessage), value: 'message' },\n\t\t\t\t\t{ label: labelForKey(keys.responseTypeRedirect), value: 'redirect' },\n\t\t\t\t],\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'message',\n\t\t\t\ttype: 'richText',\n\t\t\t\tlabel: labelForKey(keys.responseMessage),\n\t\t\t\t// Unset type (docs predating this field) means 'message', matching the client fallback.\n\t\t\t\tadmin: { condition: (_data, siblingData) => siblingData?.type !== 'redirect' },\n\t\t\t\t...(responseEditor ? { editor: responseEditor } : {}),\n\t\t\t\t...localizedIf(localizeContent),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'redirect',\n\t\t\t\ttype: 'group',\n\t\t\t\tlabel: labelForKey(keys.responseRedirect),\n\t\t\t\tadmin: {\n\t\t\t\t\tcondition: (_data, siblingData) => siblingData?.type === 'redirect',\n\t\t\t\t\thideGutter: true,\n\t\t\t\t},\n\t\t\t\tfields: redirectFields,\n\t\t\t},\n\t\t],\n\t}\n\n\tconst defaultOutcomeFields = buildDefaultOutcomeFields()\n\tconst defaultButtonFields = buildDefaultButtonFields(localizeContent)\n\t// Half-width copy of a default field for the prev/next row; merges width into whatever admin the\n\t// field already carries (a host's `buttons.fields` override may add its own) rather than replacing\n\t// it. The cast is safe: `width` is a valid admin option on every field variant, but spreading a\n\t// `Field`-typed value's `admin` back into a union-typed object literal loses the discriminant TS\n\t// needs to check it structurally.\n\tconst halfWidth = (field: Field): Field =>\n\t\t({ ...field, admin: { ...field.admin, width: '50%' } }) as Field\n\t// The three button-label fields sit at the document root now (no `buttons` group): `submit` at\n\t// the bottom of the Fields tab, `prev`/`next` in a row on the Flow tab. The `buttons.fields` seam\n\t// composes each slot from the already-localized defaults, so a host can wrap a default in a row\n\t// with its own field (e.g. an icon select) or replace it; `toFormDocument` reassembles the three\n\t// labels into `FormDocument.buttons` for the client.\n\tconst composedButtons = buttons?.fields\n\t\t? buttons.fields({ defaultFields: defaultButtonFields })\n\t\t: defaultButtonFields\n\tconst submitField = composedButtons.submit\n\tconst prevNextRow: Field = {\n\t\ttype: 'row',\n\t\t// The prev/next labels only matter once the flow has a step; the Flow tab itself is gated on\n\t\t// `multistep`. `data` (1st arg) is the whole document, so `flow.steps` reads the stored flow.\n\t\tadmin: {\n\t\t\tcondition: (data) => {\n\t\t\t\tconst steps = (data as { flow?: { steps?: unknown } })?.flow?.steps\n\t\t\t\treturn Array.isArray(steps) && steps.length >= 1\n\t\t\t},\n\t\t},\n\t\tfields: [halfWidth(composedButtons.prev), halfWidth(composedButtons.next)],\n\t}\n\n\t// Poll lifecycle config; identifiers and behavior, never localized. Lives inside the conditional\n\t// Poll tab (gated on the top-level `pollEnabled` flag), so the per-field enabled conditions are\n\t// gone; `label: false` suppresses the group header the tab label already provides.\n\t// `resultsVisibility` is defaulted and not clearable rather than `required` for the same\n\t// generated-types reason as `response.type`.\n\tconst pollGroupField: Field = {\n\t\tname: 'poll',\n\t\ttype: 'group',\n\t\tlabel: false,\n\t\tfields: [\n\t\t\t// Authored by picking from the form's poll-eligible fields; the stored value stays a plain\n\t\t\t// text field name. Select options and server validate share `pollResultsTypes`, so they\n\t\t\t// cannot drift.\n\t\t\t{\n\t\t\t\tname: 'resultsField',\n\t\t\t\ttype: 'text',\n\t\t\t\tlabel: labelForKey(keys.pollResultsField),\n\t\t\t\tvalidate: buildValidateResultsField(pollResultsTypes),\n\t\t\t\tadmin: {\n\t\t\t\t\tcomponents: {\n\t\t\t\t\t\tField: {\n\t\t\t\t\t\t\tpath: FIELD_NAME_SELECT_REF,\n\t\t\t\t\t\t\tclientProps: {\n\t\t\t\t\t\t\t\ttypes: pollResultsTypes,\n\t\t\t\t\t\t\t\tdescriptionKey: keys.pollResultsFieldDescription,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t// How the winning value(s) get decided. `mostVoted` (default) and `source` auto-resolve on close\n\t\t\t// (via the scheduled job or the results-read fallback); `manual` leaves it to an admin. Options\n\t\t\t// come from the registered `poll.types` strategies, so a host strategy appears here too, with\n\t\t\t// `mostVoted` first so an enabled poll works with no winner to hand-pick.\n\t\t\t{\n\t\t\t\tname: 'type',\n\t\t\t\ttype: 'select',\n\t\t\t\tdefaultValue: 'mostVoted',\n\t\t\t\tlabel: labelForKey(keys.pollType),\n\t\t\t\tadmin: { isClearable: false, description: labelForKey(keys.pollTypeDescription) },\n\t\t\t\toptions: orderedPollTypes\n\t\t\t\t\t.filter((strategy) => strategy.type !== 'source' || hasOptionSources)\n\t\t\t\t\t.map((strategy) => ({\n\t\t\t\t\t\tlabel: resolveDefinitionLabel(strategy.label),\n\t\t\t\t\t\tvalue: strategy.type,\n\t\t\t\t\t})),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'resultsVisibility',\n\t\t\t\ttype: 'select',\n\t\t\t\tdefaultValue: 'afterVote',\n\t\t\t\tlabel: labelForKey(keys.pollResultsVisibility),\n\t\t\t\tadmin: { isClearable: false },\n\t\t\t\toptions: [\n\t\t\t\t\t{ label: labelForKey(keys.pollVisibilityAfterVote), value: 'afterVote' },\n\t\t\t\t\t{ label: labelForKey(keys.pollVisibilityAfterClose), value: 'afterClose' },\n\t\t\t\t],\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'allowChange',\n\t\t\t\ttype: 'checkbox',\n\t\t\t\tdefaultValue: false,\n\t\t\t\tlabel: labelForKey(keys.pollAllowChange),\n\t\t\t\tadmin: { description: labelForKey(keys.pollAllowChangeDescription) },\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'closesAt',\n\t\t\t\ttype: 'date',\n\t\t\t\tlabel: labelForKey(keys.pollClosesAt),\n\t\t\t\tadmin: { date: { pickerAppearance: 'dayAndTime' } },\n\t\t\t},\n\t\t\t// Close / reopen the poll from the admin. The button toggles on the live `closesAt` and saves the\n\t\t\t// whole document (persisting an unsaved winner alongside `closesAt`) rather than calling the close\n\t\t\t// endpoint, so there is no DB-vs-form-state mismatch. Always mounted inside the (pollEnabled-gated)\n\t\t\t// Poll tab now; the button owns both states, so it carries no `admin.condition` of its own.\n\t\t\t{\n\t\t\t\tname: 'closePoll',\n\t\t\t\ttype: 'ui',\n\t\t\t\tadmin: {\n\t\t\t\t\tcomponents: { Field: CLOSE_POLL_BUTTON_REF },\n\t\t\t\t},\n\t\t\t},\n\t\t\t...buildPollOptionSourceFields(pollSourceRegistry ?? new Map()),\n\t\t\t// `winningValues` is recorded either by an admin picking from the poll's effective options\n\t\t\t// (served by `/:id/poll-options`) or by `resolvePollOutcome` (host domain logic); more than\n\t\t\t// one value records a tie. The `pollOutcomeBeforeChange` hook validates both paths and owns\n\t\t\t// the `resolvedAt` stamp. `resolvedAt` itself stays fully locked: field-level create/update\n\t\t\t// access blocks every non-override caller write (Payload silently drops the denied value\n\t\t\t// rather than erroring) while the hook's stamp, applied after access filtering, still\n\t\t\t// persists. The `poll.outcomeFields` seam receives both defaults and its return becomes the\n\t\t\t// group's fields verbatim, so a host can swap `winningValues` for its own component; the hook\n\t\t\t// still validates every stored value against the effective options, so no swap bypasses it.\n\t\t\t// `hideGutter` stays: outcome is still a group nested inside the poll group.\n\t\t\t{\n\t\t\t\tname: 'outcome',\n\t\t\t\ttype: 'group',\n\t\t\t\tlabel: labelForKey(keys.pollOutcome),\n\t\t\t\tadmin: { hideGutter: true },\n\t\t\t\tfields: outcomeFields\n\t\t\t\t\t? outcomeFields({ defaultFields: defaultOutcomeFields })\n\t\t\t\t\t: [defaultOutcomeFields.winningValues, defaultOutcomeFields.resolvedAt],\n\t\t\t},\n\t\t],\n\t}\n\n\tconst defaultFields: Field[] = [\n\t\t// The document title is the only visitor-facing text the collection renders itself: it is\n\t\t// always required (drives `useAsTitle` in the admin list/relationship views) and is passed\n\t\t// through `toFormDocument` as `FormDocument.title`. Whether and how a host renders it above\n\t\t// the fields is entirely the host's call; the plugin does not gate or duplicate it.\n\t\t{\n\t\t\tname: 'title',\n\t\t\ttype: 'text',\n\t\t\tlabel: labelForKey(keys.fieldTitle),\n\t\t\t// Visitor-facing content, so it follows `localizeContent` like the response message and\n\t\t\t// consent statement: hosts with localization get a per-locale title (and a locale-aware\n\t\t\t// `useAsTitle`), hosts without it are unaffected since Payload strips the flag. `validate`\n\t\t\t// (not `required`) keeps it mandatory only in the default locale so other locales fall back.\n\t\t\t...localizedIf(localizeContent),\n\t\t\tvalidate: validateFormTitle,\n\t\t},\n\t\t// The three form-level flags: behavior, never localized, sidebar checkboxes by default.\n\t\t// `multistep` gates the Flow tab and the client's step navigation; `pollEnabled` gates the\n\t\t// Poll tab and marks the form a poll; `persistSubmissions` (default checked) tells the plugin\n\t\t// whether to keep a submission's row after its actions run, or prune it. The `settings.fields`\n\t\t// seam composes them from the defaults, mirroring `buttons.fields`.\n\t\t...composeSettingsFields(settings),\n\t\t// Unnamed tabs are presentational: their fields stay at the document root. An unnamed tab's\n\t\t// `admin.condition` receives the whole document as its 2nd arg, so the Flow and Poll tabs gate\n\t\t// on the root-level flags. The poll group nests its config under `form.poll` as before.\n\t\t{\n\t\t\ttype: 'tabs',\n\t\t\ttabs: [\n\t\t\t\t{ label: labelForKey(keys.tabFields), fields: [fieldsField, submitField] },\n\t\t\t\t{\n\t\t\t\t\tlabel: labelForKey(keys.tabFlow),\n\t\t\t\t\tadmin: { condition: (_data, siblingData) => siblingData?.multistep === true },\n\t\t\t\t\tfields: [flowField, prevNextRow],\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: labelForKey(keys.pollGroup),\n\t\t\t\t\tadmin: { condition: (_data, siblingData) => siblingData?.pollEnabled === true },\n\t\t\t\t\tfields: [pollGroupField],\n\t\t\t\t},\n\t\t\t\t{ label: labelForKey(keys.tabActions), fields: [actionsField] },\n\t\t\t\t{ label: labelForKey(keys.tabResponse), fields: [responseField] },\n\t\t\t],\n\t\t},\n\t]\n\n\t// When `consent.sources` is set, resolve the visitor-facing consent statements onto every read of\n\t// a form doc (REST, local API, relationship population), so a client/modal fetch renders consent,\n\t// not only the RSC path that calls `resolveConsentStatements` itself. Fails open: a resolver outage\n\t// must never break form/admin/relationship reads, and the renderer already tolerates a missing\n\t// statement. `resolveConsentStatements` returns `{}` without calling the resolver when the form has\n\t// no consent fields, and entries are cached per request+form, so the cost stays bounded.\n\tconst consentAfterRead: CollectionAfterReadHook | undefined =\n\t\tconsentSources && consentResolveOnRead !== false\n\t\t\t? async ({ doc, req }) => {\n\t\t\t\t\tconst id = (doc as { id?: unknown }).id\n\t\t\t\t\tconst key = id == null ? undefined : String(id)\n\t\t\t\t\t// Re-entrance guard: a host resolver that reads this same form back (threading req) would\n\t\t\t\t\t// otherwise re-enter this hook and recurse. Payload runs collection afterRead concurrently\n\t\t\t\t\t// across list docs on one shared req.context, so this must be a per-id Set (a boolean would\n\t\t\t\t\t// make sibling docs skip), mutated in place and never reassigned after fan-out. Mirrors\n\t\t\t\t\t// @payloadcms/plugin-search's syncDocAsSearchIndex guard.\n\t\t\t\t\tif (key !== undefined && req.context) {\n\t\t\t\t\t\tconst inFlight =\n\t\t\t\t\t\t\t(req.context[CONSENT_AFTER_READ_GUARD] as Set<string> | undefined) ??\n\t\t\t\t\t\t\tnew Set<string>()\n\t\t\t\t\t\tif (inFlight.has(key)) {\n\t\t\t\t\t\t\treturn doc\n\t\t\t\t\t\t}\n\t\t\t\t\t\tinFlight.add(key)\n\t\t\t\t\t\treq.context[CONSENT_AFTER_READ_GUARD] = inFlight\n\t\t\t\t\t}\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst statements = await resolveConsentStatements({\n\t\t\t\t\t\t\tpayload: req.payload,\n\t\t\t\t\t\t\treq,\n\t\t\t\t\t\t\tform: doc,\n\t\t\t\t\t\t\tsources: consentSources,\n\t\t\t\t\t\t})\n\t\t\t\t\t\tif (Object.keys(statements).length > 0) {\n\t\t\t\t\t\t\t;(doc as Record<string, unknown>).consentStatements = statements\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\treq.payload.logger?.warn(`form-builder consent afterRead: ${String(error)}`)\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tif (key !== undefined && req.context) {\n\t\t\t\t\t\t\t;(req.context[CONSENT_AFTER_READ_GUARD] as Set<string> | undefined)?.delete(key)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn doc\n\t\t\t\t}\n\t\t\t: undefined\n\n\t// When calc sources are registered, resolve the values a form's expressions reference onto every\n\t// read of its doc, stamped as `doc.calcResolved` (`toFormDocument` passes it through), so the\n\t// client's live preview computes with real values. Fails open: a resolver outage must never break\n\t// form/admin/relationship reads; the preview degrades to 0 and submit re-resolves loudly.\n\tconst calcAfterRead: CollectionAfterReadHook | undefined =\n\t\tcalcSources && Object.keys(calcSources).length > 0\n\t\t\t? async ({ doc, req }) => {\n\t\t\t\t\tconst fields = Array.isArray((doc as { fields?: unknown }).fields)\n\t\t\t\t\t\t? ((doc as { fields: unknown[] }).fields as FormFieldInstance[])\n\t\t\t\t\t\t: []\n\t\t\t\t\tif (!calcUsesSources(fields)) {\n\t\t\t\t\t\treturn doc\n\t\t\t\t\t}\n\t\t\t\t\tconst id = (doc as { id?: unknown }).id\n\t\t\t\t\tconst key = id == null ? undefined : String(id)\n\t\t\t\t\t// Re-entrance guard: a host resolver that reads this same form back (threading req) would\n\t\t\t\t\t// otherwise re-enter this hook and recurse. Per-id Set, mutated in place, exactly like\n\t\t\t\t\t// `consentAfterRead` above (afterRead fans out concurrently over one shared req.context).\n\t\t\t\t\tif (key !== undefined && req.context) {\n\t\t\t\t\t\tconst inFlight =\n\t\t\t\t\t\t\t(req.context[CALC_AFTER_READ_GUARD] as Set<string> | undefined) ?? new Set<string>()\n\t\t\t\t\t\tif (inFlight.has(key)) {\n\t\t\t\t\t\t\treturn doc\n\t\t\t\t\t\t}\n\t\t\t\t\t\tinFlight.add(key)\n\t\t\t\t\t\treq.context[CALC_AFTER_READ_GUARD] = inFlight\n\t\t\t\t\t}\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst resolved = await resolveCalcContext({\n\t\t\t\t\t\t\tfields,\n\t\t\t\t\t\t\tsources: calcSources,\n\t\t\t\t\t\t\tform: doc as { id: number | string } & Record<string, unknown>,\n\t\t\t\t\t\t\tpayload: req.payload,\n\t\t\t\t\t\t\treq,\n\t\t\t\t\t\t})\n\t\t\t\t\t\t;(doc as Record<string, unknown>).calcResolved = resolved\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\treq.payload.logger?.warn(`form-builder calc afterRead: ${String(error)}`)\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tif (key !== undefined && req.context) {\n\t\t\t\t\t\t\t;(req.context[CALC_AFTER_READ_GUARD] as Set<string> | undefined)?.delete(key)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn doc\n\t\t\t\t}\n\t\t\t: undefined\n\n\tconst defaultEndpoints = buildFormsEndpoints({\n\t\tresultsAccess,\n\t\tpollResultsTypes,\n\t\tpollVotesEnabled,\n\t\tconsentSources,\n\t\tfromAddresses,\n\t\tfromSources,\n\t\tdepartments,\n\t})\n\n\treturn {\n\t\t...(overrides ?? {}),\n\t\tslug: FORMS_SLUG,\n\t\tlabels: {\n\t\t\tsingular: labelForKey(keys.collectionFormSingular),\n\t\t\tplural: labelForKey(keys.collectionFormPlural),\n\t\t\t...(overrides?.labels ?? {}),\n\t\t},\n\t\tadmin: {\n\t\t\tgroup: 'Forms',\n\t\t\tuseAsTitle: 'title',\n\t\t\tdefaultColumns: ['title', 'fields', 'flow', 'pollEnabled', 'updatedAt'],\n\t\t\t...(overrides?.admin ?? {}),\n\t\t},\n\t\taccess: { read: () => true, ...(overrides?.access ?? {}) },\n\t\thooks: {\n\t\t\t...(overrides?.hooks ?? {}),\n\t\t\t// beforeValidate normalizes conditions and flow; consumer hooks run after\n\t\t\tbeforeValidate: [\n\t\t\t\tbeforeValidate,\n\t\t\t\tactionConfigBeforeValidate,\n\t\t\t\t...(overrides?.hooks?.beforeValidate ?? []),\n\t\t\t],\n\t\t\tbeforeChange: [pollOutcomeBeforeChange, ...(overrides?.hooks?.beforeChange ?? [])],\n\t\t\tafterChange: [pollCloseAfterChange, ...(overrides?.hooks?.afterChange ?? [])],\n\t\t\tafterRead: [\n\t\t\t\t...(consentAfterRead ? [consentAfterRead] : []),\n\t\t\t\t...(calcAfterRead ? [calcAfterRead] : []),\n\t\t\t\t...(overrides?.hooks?.afterRead ?? []),\n\t\t\t],\n\t\t},\n\t\tendpoints: [\n\t\t\t...defaultEndpoints,\n\t\t\t...(Array.isArray(overrides?.endpoints) ? overrides.endpoints : []),\n\t\t],\n\t\tfields: overrides?.fields ? overrides.fields({ defaultFields }) : defaultFields,\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"forms.js","names":[],"sources":["../../src/collections/forms.ts"],"sourcesContent":["import {\n\ttype CollectionAfterChangeHook,\n\ttype CollectionAfterReadHook,\n\ttype CollectionBeforeValidateHook,\n\ttype CollectionConfig,\n\ttype CollectionSlug,\n\ttype Field,\n\ttype PayloadRequest,\n\ttype TextFieldSingleValidation,\n\tValidationError,\n} from 'payload'\nimport type { RichTextBodyOption } from '../actions/body/serializeBody'\nimport { buildActionBlocks } from '../actions/buildActionBlocks'\nimport type { FromAddressesResolver, FromAddressSourceRegistry } from '../actions/fromAddresses'\nimport type { ActionRegistry } from '../actions/registry'\nimport type { ActionInstance } from '../actions/runActions'\nimport type { FormResultsAccess } from '../aggregation/resolveResultsRequest'\nimport { type CalcAllowed, normalizeCalc } from '../calc/normalizeCalc'\nimport type { CalcSource } from '../calc/registry'\nimport { calcUsesSources, resolveCalcContext } from '../calc/resolveCalcContext'\nimport { buildConditionTypeMap } from '../conditions/conditionType'\nimport {\n\tbuildOperandTypes,\n\ttype FieldRow,\n\tnormalizeFormConditions,\n\tnormalizeWhere,\n} from '../conditions/normalizeConditions'\nimport { resolveConsentStatements } from '../consent/resolveConsentStatements'\nimport type { ConsentSourcesResolver } from '../consent/types'\nimport type { DepartmentEmailsResolver } from '../email/departments'\nimport { buildFieldBlocks } from '../fields/buildFieldBlocks'\nimport { fieldNamesOfType } from '../fields/fieldNamesOfType'\nimport { localizedIf } from '../fields/localizedIf'\nimport type { FieldTypeRegistry } from '../fields/registry'\nimport { normalizeFlow } from '../flow/normalizeFlow'\nimport { END_OF_FORM } from '../flow/types'\nimport { pollConfigOf } from '../form/pollState'\nimport { isLoggedIn } from '../plugin/access'\nimport type { CollectionOverrides } from '../plugin/collectionOverrides'\nimport {\n\tDEFAULT_COLLECTION_SLUGS,\n\ttype FormBuilderCollectionSlugs,\n} from '../plugin/collectionSlugs'\nimport { buildPollOptionSourceFields } from '../poll/buildPollOptionSourceFields'\nimport { enqueuePollClose } from '../poll/closeJob'\nimport { pollOutcomeBeforeChange } from '../poll/outcomeBeforeChange'\nimport { buildDefaultOutcomeFields, type OutcomeFieldsOverride } from '../poll/outcomeFields'\nimport { type PollTypeRegistry, resolvePollTypes } from '../poll/pollTypeRegistry'\nimport type { PollOptionSourceRegistry } from '../poll/registry'\nimport { buildValidateResultsField, pollEligibleTypes } from '../poll/resultsField'\nimport type { FormFieldInstance } from '../submissions/types'\nimport { keys } from '../translations/keys'\nimport { asTranslate, labelForKey, resolveDefinitionLabel } from '../translations/server'\nimport type { ValidationRuleRegistry } from '../validation/registry'\nimport { validateUrl } from '../validation/validateUrl'\nimport { type ButtonsOption, buildDefaultButtonFields } from './buttonFields'\nimport { buildFormsEndpoints } from './formsEndpoints'\nimport type { ResponseOption } from './redirectFields'\nimport { composeSettingsFields, type SettingsOption } from './settingsFields'\n\n/** `req.context` key under which `consentAfterRead` tracks the form ids it is currently resolving, to break re-entrant reads. */\nconst CONSENT_AFTER_READ_GUARD = 'formBuilderConsentAfterReadInFlight'\n\n/** `req.context` key under which `calcAfterRead` tracks the form ids it is currently resolving, to break re-entrant reads. */\nconst CALC_AFTER_READ_GUARD = 'formBuilderCalcAfterReadInFlight'\n\n/**\n * Require the title in the default locale (and on hosts without localization), but let it be empty in\n * other locales so a localized form falls back to the default-locale title rather than forcing a\n * translation for every locale. Payload's field-level `required` cannot express this: it enforces per\n * write-locale, which would make a title mandatory in every locale and break the documented fallback.\n */\nconst validateFormTitle: TextFieldSingleValidation = (value, { req }) => {\n\tconst localization = req.payload.config.localization\n\tconst defaultLocale = localization ? localization.defaultLocale : undefined\n\tconst enforced =\n\t\tdefaultLocale === undefined || req.locale === undefined || req.locale === defaultLocale\n\tif (enforced && (typeof value !== 'string' || value.trim().length === 0)) {\n\t\treturn req.t('validation:required')\n\t}\n\treturn true\n}\n\n/**\n * Field-level validation for the raw flow JSON: step ids must be non-empty, unique, and not the\n * reserved `END_OF_FORM` sentinel, and every explicit step-id reference (a string `next`, a\n * transition `to`) must resolve to a known step. `next: null` (explicit end of form) and an\n * absent `next` (fall through to the next step in array order) are always valid. On full-document\n * saves the beforeValidate pass has already run `normalizeFlow` and laundered dangling\n * references, so the unknown-target checks mainly protect partial API updates that send `flow`\n * without `fields`.\n */\nconst validateFlow = (\n\traw: unknown,\n\t{ req }: { data?: unknown; req: PayloadRequest }\n): string | true => {\n\tif (raw === null || raw === undefined) return true\n\tconst r = raw as Record<string, unknown>\n\tif (!Array.isArray(r.steps)) return true\n\tconst steps = r.steps as Array<Record<string, unknown>>\n\tconst t = asTranslate(req.t)\n\tconst emptyIdStep = steps.find((s) => typeof s?.id !== 'string' || s.id.length === 0)\n\tif (emptyIdStep) return t(keys.flowStepIdEmpty)\n\tif (steps.some((s) => s.id === END_OF_FORM)) {\n\t\treturn t(keys.flowStepIdReserved).replace('{id}', END_OF_FORM)\n\t}\n\tconst ids = steps.map((s) => s.id as string)\n\tif (new Set(ids).size !== ids.length) {\n\t\treturn t(keys.flowDuplicateStepIds)\n\t}\n\tconst idSet = new Set(ids)\n\tfor (const step of steps) {\n\t\tconst id = step.id as string\n\t\tif (typeof step.next === 'string' && step.next.length > 0 && !idSet.has(step.next)) {\n\t\t\treturn t(keys.flowUnknownNext).replace('{id}', id).replace('{next}', step.next)\n\t\t}\n\t\tif (Array.isArray(step.transitions)) {\n\t\t\tfor (const trans of step.transitions as Array<Record<string, unknown>>) {\n\t\t\t\tif (typeof trans?.to === 'string' && trans.to.length > 0 && !idSet.has(trans.to)) {\n\t\t\t\t\treturn t(keys.flowUnknownTransition).replace('{id}', id).replace('{to}', trans.to)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn true\n}\n\n/** How many steps the caller actually submitted, before normalization strips/collapses the flow. */\nconst providedFlowStepCount = (raw: unknown): number => {\n\tif (raw === null || typeof raw !== 'object') return 0\n\tconst steps = (raw as { steps?: unknown }).steps\n\treturn Array.isArray(steps) ? steps.length : 0\n}\n\n/**\n * Stamp the configured uploads collection slug onto every `file` block (including repeater\n * sub-fields) on each save. The block carries a hidden `uploadsCollection` field so the slug\n * reaches the client renderer through the form document; the server always overwrites it from\n * plugin config, so the stored value is never author- or client-controlled.\n */\nconst stampFileCollections = (rows: FieldRow[], slug: string): void => {\n\tfor (const row of rows) {\n\t\tif (row.blockType === 'file') {\n\t\t\t;(row as { uploadsCollection?: string }).uploadsCollection = slug\n\t\t}\n\t\tconst subFields = (row as { subFields?: unknown }).subFields\n\t\tif (Array.isArray(subFields)) {\n\t\t\tstampFileCollections(subFields as FieldRow[], slug)\n\t\t}\n\t}\n}\n\ntype BuildFormsCollectionArgs = {\n\tregistry: FieldTypeRegistry\n\truleRegistry: ValidationRuleRegistry\n\t/** Registered calc extension names; expressions referencing anything else are dropped on save. */\n\tcalcAllowed?: CalcAllowed\n\t/** Registered calc sources; when a form's expressions use any, an afterRead hook stamps `doc.calcResolved`. */\n\tcalcSources?: Record<string, CalcSource>\n\t/**\n\t * The plugin `consent.sources` option. Present: the `/:id/consent-sources` endpoint backing the\n\t * consent field's source select is registered. Absent: neither it nor the consent field type exists.\n\t */\n\tconsentSources?: ConsentSourcesResolver\n\t/** Plugin `consent.resolveOnRead` (default true); `false` skips the per-read consent afterRead hook. */\n\tconsentResolveOnRead?: boolean\n\tactionRegistry?: ActionRegistry\n\tlocalizeContent?: boolean\n\t/** The plugin `richText` option; `responseEditor ?? editor` sets the response message field's editor. */\n\trichText?: RichTextBodyOption\n\t/** The host-owned uploads collection slug from plugin config; absent when uploads are disabled. */\n\tuploadsCollectionSlug?: string\n\t/** Host seam gating anonymous results reads (plugin option `results.access`). */\n\tresultsAccess?: FormResultsAccess\n\t/**\n\t * Whether the hidden tally store is registered (`poll.votes !== false`). Threads into the poll\n\t * endpoints (tally-backed reads) and, when false, forbids poll forms from disabling\n\t * `persistSubmissions`: with neither store nor submissions there would be nothing to count.\n\t */\n\tpollVotesEnabled?: boolean\n\t/** Registered poll option sources (plugin option `poll.sources`); empty registry means no source fields. */\n\tpollSourceRegistry?: PollOptionSourceRegistry\n\t/** Registered poll outcome strategies (`poll.types`); drives the poll `type` select options. Defaults to the built-ins. */\n\tpollTypeRegistry?: PollTypeRegistry\n\t/** The plugin `poll.outcomeFields` seam; composes the outcome group from the two default fields. */\n\toutcomeFields?: OutcomeFieldsOverride\n\t/** The plugin `buttons` option; `fields` composes the `{ submit, prev, next }` labels from the localized defaults. */\n\tbuttons?: ButtonsOption\n\t/** The plugin `settings` option; `fields` composes the sidebar flag checkboxes from the defaults. */\n\tsettings?: SettingsOption\n\t/** The plugin `response` option; `redirect.fields` composes the `response.redirect` group from its default fields. */\n\tresponse?: ResponseOption\n\t/**\n\t * The plugin `email.fromAddresses` option. Either this or `fromSources` present: both email\n\t * actions gain a `from` select and the `/:id/from-addresses` endpoint is registered. Both\n\t * absent: neither exists.\n\t */\n\tfromAddresses?: FromAddressesResolver\n\t/** The plugin `email.fromSources` option: send-time-resolved senders served ahead of the literals. */\n\tfromSources?: FromAddressSourceRegistry\n\t/**\n\t * The plugin `email.departments` option. Present: the `emailTeam` `to` becomes a department select\n\t * and the `/:id/departments` endpoint backing it is registered. Absent: `to` stays a plain field.\n\t */\n\tdepartments?: DepartmentEmailsResolver\n\t/**\n\t * The plugin `redirectRelationships` option. Non-empty: `response.redirect` gains a polymorphic\n\t * `reference` relationship field so an author can redirect to an internal document instead of\n\t * (or alongside) a URL. Absent or empty: no `reference` field exists, matching today's URL-only\n\t * redirect.\n\t */\n\tredirectRelationships?: CollectionSlug[]\n\t/** Resolved plugin collection slugs (after host `overrides.*.slug`); defaults when omitted. */\n\tslugs?: FormBuilderCollectionSlugs\n\toverrides?: CollectionOverrides\n}\n\nconst isPlainObject = (value: unknown): value is Record<string, unknown> => {\n\tif (value == null || typeof value !== 'object') {\n\t\treturn false\n\t}\n\tconst proto = Object.getPrototypeOf(value)\n\treturn proto === Object.prototype || proto === null\n}\n\n/**\n * Mirror how Payload persists a partial update: plain objects merge recursively (an unsent group\n * sibling keeps its stored value), arrays and everything else are replaced by the delta, and an\n * explicit `null` survives as null. A shallow spread would hand `validateConfig` a group missing\n * the siblings the update did not send.\n */\nconst mergeSavedShape = (\n\toriginal: Record<string, unknown>,\n\tdelta: Record<string, unknown>\n): Record<string, unknown> => {\n\tconst merged: Record<string, unknown> = { ...original }\n\tfor (const [key, value] of Object.entries(delta)) {\n\t\tconst base = merged[key]\n\t\tmerged[key] = isPlainObject(base) && isPlainObject(value) ? mergeSavedShape(base, value) : value\n\t}\n\treturn merged\n}\n\nexport const buildFormsCollection = ({\n\toverrides,\n\tslugs = DEFAULT_COLLECTION_SLUGS,\n\tregistry,\n\truleRegistry,\n\tcalcAllowed,\n\tcalcSources,\n\tconsentSources,\n\tconsentResolveOnRead,\n\tactionRegistry = new Map(),\n\tlocalizeContent = true,\n\trichText,\n\tuploadsCollectionSlug,\n\tresultsAccess,\n\tpollVotesEnabled = false,\n\tpollSourceRegistry,\n\tpollTypeRegistry,\n\toutcomeFields,\n\tbuttons,\n\tsettings,\n\tresponse,\n\tfromAddresses,\n\tfromSources,\n\tdepartments,\n\tredirectRelationships,\n}: BuildFormsCollectionArgs): CollectionConfig => {\n\tconst conditionTypes = buildConditionTypeMap(registry)\n\tconst pollTypes = pollTypeRegistry ?? resolvePollTypes()\n\tconst pollResultsTypes = pollEligibleTypes(registry)\n\tconst bareTypes = new Set(\n\t\t[...registry.values()].filter((d) => d.bare === true).map((d) => d.type)\n\t)\n\t// Handed to the FlowBuilder so it can list bare (nameless) blocks in the step picker: slug ->\n\t// label (an i18n key or literal, resolved client-side).\n\tconst bareTypeLabels = Object.fromEntries(\n\t\t[...registry.values()].filter((d) => d.bare === true).map((d) => [d.type, d.label])\n\t)\n\t// The success `response` message field's editor: `richText.responseEditor` overrides the plugin-wide\n\t// `richText.editor` for this field alone (mirroring `bodyEditor` for action bodies).\n\tconst responseEditor = richText?.responseEditor ?? richText?.editor\n\tconst FLOW_BUILDER_REF = '@10x-media/form-builder/client#FlowBuilder'\n\tconst FIELD_NAME_SELECT_REF = '@10x-media/form-builder/client#FieldNameSelect'\n\tconst FLOW_STEPS_CELL_REF = '@10x-media/form-builder/client#FlowStepsCell'\n\tconst FIELD_COUNT_CELL_REF = '@10x-media/form-builder/client#FieldCountCell'\n\tconst CLOSE_POLL_BUTTON_REF = '@10x-media/form-builder/client#ClosePollButton'\n\t// The `source` outcome strategy delegates to the poll's option source, so it is useless without one:\n\t// offer it in the author-facing select only when option sources are registered. The strategy stays in\n\t// the registry regardless, so `resolvePollOutcome` and host code can still address it.\n\tconst hasOptionSources = (pollSourceRegistry?.size ?? 0) > 0\n\t// `mostVoted` leads the author-facing select because it is the default: an enabled poll works\n\t// immediately, with no winner to hand-pick. Only the select order changes; the registry keeps its\n\t// own order (host strategies stay after the built-ins).\n\tconst orderedPollTypes = [\n\t\t...[...pollTypes.values()].filter((strategy) => strategy.type === 'mostVoted'),\n\t\t...[...pollTypes.values()].filter((strategy) => strategy.type !== 'mostVoted'),\n\t]\n\n\tconst beforeValidate: CollectionBeforeValidateHook = ({ data, originalDoc, req }) => {\n\t\t// Without the tally store, pruned submissions would erase every vote: a poll form may only\n\t\t// turn `persistSubmissions` off while the store is registered to carry the counts. Evaluated\n\t\t// on the incoming partial merged over the stored doc (outcomeBeforeChange precedent), so a\n\t\t// PATCH flipping either flag alone cannot slip past the guard.\n\t\tif (!pollVotesEnabled) {\n\t\t\tconst pollOn = (data?.pollEnabled ?? originalDoc?.pollEnabled) === true\n\t\t\tconst persistOff = (data?.persistSubmissions ?? originalDoc?.persistSubmissions) === false\n\t\t\tif (pollOn && persistOff) {\n\t\t\t\tthrow new ValidationError(\n\t\t\t\t\t{\n\t\t\t\t\t\tcollection: slugs.forms,\n\t\t\t\t\t\terrors: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tpath: 'persistSubmissions',\n\t\t\t\t\t\t\t\tmessage: asTranslate(req.t)(keys.pollNeedsPersistedSubmissions),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t},\n\t\t\t\t\treq.t\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\t{\n\t\t\t// Changeable votes update the voter's stored submission in place, so `allowChange` cannot\n\t\t\t// coexist with pruning: a pruned row would make the next \"change\" a second, double-counted\n\t\t\t// vote. Same merged-partial evaluation as the guard above.\n\t\t\tconst pollOn = (data?.pollEnabled ?? originalDoc?.pollEnabled) === true\n\t\t\tconst persistOff = (data?.persistSubmissions ?? originalDoc?.persistSubmissions) === false\n\t\t\tconst allowChangeOn =\n\t\t\t\t(pollConfigOf(data?.poll)?.allowChange ?? pollConfigOf(originalDoc?.poll)?.allowChange) ===\n\t\t\t\ttrue\n\t\t\tif (pollOn && persistOff && allowChangeOn) {\n\t\t\t\tthrow new ValidationError(\n\t\t\t\t\t{\n\t\t\t\t\t\tcollection: slugs.forms,\n\t\t\t\t\t\terrors: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tpath: 'persistSubmissions',\n\t\t\t\t\t\t\t\tmessage: asTranslate(req.t)(keys.pollAllowChangeNeedsPersistedSubmissions),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t},\n\t\t\t\t\treq.t\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\tif (data && Array.isArray(data.fields)) {\n\t\t\tconst normalized: FieldRow[] = normalizeFormConditions(\n\t\t\t\tdata.fields as FieldRow[],\n\t\t\t\tconditionTypes\n\t\t\t)\n\t\t\tfor (const field of normalized) {\n\t\t\t\tif ('expression' in field) {\n\t\t\t\t\tfield.expression = normalizeCalc(field.expression, calcAllowed)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (uploadsCollectionSlug) {\n\t\t\t\tstampFileCollections(normalized, uploadsCollectionSlug)\n\t\t\t}\n\t\t\tdata.fields = normalized\n\t\t\t// Flow step assignments store field keys: machine names for named fields, block row ids\n\t\t\t// for bare (nameless) blocks. Mirrors `fieldKey` over the raw rows.\n\t\t\tconst fieldKeys = normalized\n\t\t\t\t.map((field: FieldRow) => {\n\t\t\t\t\tif (typeof field.name === 'string' && field.name.length > 0) {\n\t\t\t\t\t\treturn field.name\n\t\t\t\t\t}\n\t\t\t\t\treturn bareTypes.has(field.blockType) && field.id != null ? String(field.id) : undefined\n\t\t\t\t})\n\t\t\t\t.filter((key): key is string => key !== undefined)\n\t\t\t// Launder flow transition `when` clauses with the same operand-type rules as field conditions,\n\t\t\t// so a transition referencing a deleted field is dropped, not left as a route that always\n\t\t\t// matches at navigation time and force-routes the visitor.\n\t\t\tconst operandTypes = buildOperandTypes(normalized, conditionTypes)\n\t\t\tconst normalizedFlow = normalizeFlow(data.flow, fieldKeys, (w) =>\n\t\t\t\tnormalizeWhere(w, operandTypes)\n\t\t\t)\n\t\t\t// A flow the author built but that collapses to fewer than two valid steps would\n\t\t\t// otherwise vanish silently. Surface it instead of discarding their work.\n\t\t\tif (providedFlowStepCount(data.flow) > 0 && normalizedFlow === undefined) {\n\t\t\t\tthrow new ValidationError(\n\t\t\t\t\t{\n\t\t\t\t\t\tcollection: slugs.forms,\n\t\t\t\t\t\terrors: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tpath: 'flow',\n\t\t\t\t\t\t\t\tmessage: asTranslate(req.t)(keys.flowNeedsTwoSteps),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t},\n\t\t\t\t\treq.t\n\t\t\t\t)\n\t\t\t}\n\t\t\tdata.flow = normalizedFlow\n\t\t\t// A poll needs a field whose answers are the votes. With the poll enabled and no vote field\n\t\t\t// chosen (and the outcome not coming from an option source), bind one: auto-fill the sole\n\t\t\t// eligible field for a one-choice form, otherwise surface a clear error rather than storing a\n\t\t\t// poll that can never aggregate. The optionSource/`source` exemption preserves domain-driven\n\t\t\t// polls that have no on-form vote field. `buildValidateResultsField` still checks a set value;\n\t\t\t// this is the enforcer for enabled polls.\n\t\t\tif (data.pollEnabled === true) {\n\t\t\t\tconst poll =\n\t\t\t\t\tdata.poll != null && typeof data.poll === 'object'\n\t\t\t\t\t\t? (data.poll as Record<string, unknown>)\n\t\t\t\t\t\t: {}\n\t\t\t\tconst resultsField = typeof poll.resultsField === 'string' ? poll.resultsField.trim() : ''\n\t\t\t\tconst optionSource = typeof poll.optionSource === 'string' ? poll.optionSource.trim() : ''\n\t\t\t\tconst sourceDriven = optionSource.length > 0 || poll.type === 'source'\n\t\t\t\tif (resultsField.length === 0 && !sourceDriven) {\n\t\t\t\t\tconst eligible = fieldNamesOfType(data.fields, pollResultsTypes)\n\t\t\t\t\tif (eligible.length === 1) {\n\t\t\t\t\t\tdata.poll = { ...poll, resultsField: eligible[0] }\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst messageKey =\n\t\t\t\t\t\t\teligible.length > 1 ? keys.pollVoteFieldChoose : keys.pollVoteFieldMissing\n\t\t\t\t\t\tthrow new ValidationError(\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcollection: slugs.forms,\n\t\t\t\t\t\t\t\terrors: [{ path: 'poll.resultsField', message: asTranslate(req.t)(messageKey) }],\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\treq.t\n\t\t\t\t\t\t)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn data\n\t}\n\n\t// Save-time seam for an action's own cross-field checks: each stored instance runs its\n\t// definition's `validateConfig` against the merged form data, and every refusal lands on the\n\t// action block itself (`actions.<index>`), not on whichever config field happens to be required.\n\t// Stored actions are validated even when a partial update does not resend them, because the\n\t// check may span the whole form (an action body referencing a field the update just deleted).\n\tconst actionConfigBeforeValidate: CollectionBeforeValidateHook = async ({\n\t\tdata,\n\t\toriginalDoc,\n\t\treq,\n\t}) => {\n\t\tconst instances = (data?.actions ?? originalDoc?.actions) as ActionInstance[] | undefined\n\t\tif (!Array.isArray(instances) || instances.length === 0) {\n\t\t\treturn data\n\t\t}\n\t\tconst merged = mergeSavedShape(\n\t\t\t(originalDoc ?? {}) as Record<string, unknown>,\n\t\t\t(data ?? {}) as Record<string, unknown>\n\t\t)\n\t\tconst errors: { path: string; message: string }[] = []\n\t\tfor (const [index, instance] of instances.entries()) {\n\t\t\tconst validate = actionRegistry.get(instance.blockType)?.validateConfig\n\t\t\tif (!validate) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tconst verdict = await validate(instance, { data: merged, req })\n\t\t\tif (verdict !== true) {\n\t\t\t\terrors.push({ path: `actions.${index}`, message: verdict })\n\t\t\t}\n\t\t}\n\t\tif (errors.length > 0) {\n\t\t\tthrow new ValidationError({ collection: slugs.forms, errors }, req.t)\n\t\t}\n\t\treturn data\n\t}\n\n\t// After every save, (re)schedule the poll's auto-close job when it applies (enabled + closesAt +\n\t// unresolved + non-manual strategy + a job runner present). Best-effort and non-throwing, so it\n\t// never affects the save; without a runner the resolve-on-read fallback in the results endpoint\n\t// heals the outcome instead.\n\tconst pollCloseAfterChange: CollectionAfterChangeHook = async ({ doc, req }) => {\n\t\tawait enqueuePollClose({ payload: req.payload, form: doc, req })\n\t\treturn doc\n\t}\n\n\tconst fieldsField: Field = {\n\t\tname: 'fields',\n\t\ttype: 'blocks',\n\t\tblocks: buildFieldBlocks({ registry, ruleRegistry, localize: localizeContent }),\n\t\tadmin: { components: { Cell: FIELD_COUNT_CELL_REF } },\n\t}\n\n\tconst flowField: Field = {\n\t\tname: 'flow',\n\t\ttype: 'json',\n\t\tvalidate: validateFlow,\n\t\tadmin: {\n\t\t\tcomponents: {\n\t\t\t\tCell: FLOW_STEPS_CELL_REF,\n\t\t\t\tField: { path: FLOW_BUILDER_REF, clientProps: { conditionTypes, bareTypeLabels } },\n\t\t\t},\n\t\t},\n\t\t// Narrows the generated TypeScript type from opaque JSON to FormFlow so callers\n\t\t// don't need a cast. Keep this in sync with src/flow/types.ts.\n\t\ttypescriptSchema: [\n\t\t\t() => ({\n\t\t\t\ttype: 'object' as const,\n\t\t\t\trequired: ['steps'],\n\t\t\t\tadditionalProperties: false,\n\t\t\t\tproperties: {\n\t\t\t\t\tsteps: {\n\t\t\t\t\t\ttype: 'array' as const,\n\t\t\t\t\t\titems: {\n\t\t\t\t\t\t\ttype: 'object' as const,\n\t\t\t\t\t\t\trequired: ['id'],\n\t\t\t\t\t\t\tadditionalProperties: true,\n\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\tid: { type: 'string' as const },\n\t\t\t\t\t\t\t\ttitle: { type: 'string' as const },\n\t\t\t\t\t\t\t\tfields: { type: 'array' as const, items: { type: 'string' as const } },\n\t\t\t\t\t\t\t\tnext: { type: ['string', 'null'] as ('string' | 'null')[] },\n\t\t\t\t\t\t\t\ttransitions: {\n\t\t\t\t\t\t\t\t\ttype: 'array' as const,\n\t\t\t\t\t\t\t\t\titems: {\n\t\t\t\t\t\t\t\t\t\ttype: 'object' as const,\n\t\t\t\t\t\t\t\t\t\trequired: ['to'],\n\t\t\t\t\t\t\t\t\t\tadditionalProperties: true,\n\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\tto: { type: 'string' as const },\n\t\t\t\t\t\t\t\t\t\t\twhen: { type: 'object' as const, additionalProperties: true },\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t],\n\t}\n\n\tconst actionsField: Field = {\n\t\tname: 'actions',\n\t\ttype: 'blocks',\n\t\tblocks: buildActionBlocks(actionRegistry),\n\t\tlabel: labelForKey(keys.configActions),\n\t\t// Action config can contain secrets (e.g. signedWebhook.secret). The collection\n\t\t// itself is publicly readable so forms can be rendered by anonymous clients, but\n\t\t// action config must never be exposed to anonymous callers.\n\t\taccess: { read: isLoggedIn },\n\t}\n\n\t// Optional polymorphic reference to an internal document, for `response.redirect` to point at\n\t// instead of (or alongside) a URL. Absent unless `redirectRelationships` is a non-empty array:\n\t// always polymorphic even for one slug (the consent `page` field's precedent), so a host adding\n\t// a second collection later never changes the stored shape. The plugin never resolves this to a\n\t// URL itself; `toFormDocument` passes the raw `{ relationTo, value }` through for the host to\n\t// resolve, since only the host knows its own routing.\n\tconst redirectReferenceField: Field[] =\n\t\tredirectRelationships && redirectRelationships.length > 0\n\t\t\t? [\n\t\t\t\t\t{\n\t\t\t\t\t\tname: 'reference',\n\t\t\t\t\t\ttype: 'relationship',\n\t\t\t\t\t\trelationTo: redirectRelationships,\n\t\t\t\t\t\tlabel: labelForKey(keys.responseRedirectReference),\n\t\t\t\t\t\tadmin: { description: labelForKey(keys.responseRedirectReferenceDescription) },\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t: []\n\n\t// The redirect group's fields: the `url` text field plus the optional polymorphic `reference`.\n\t// The `response.redirect.fields` seam composes them (mirroring `buttons.fields`), so a host can\n\t// prepend a custom link field, swap `url` for their own picker, or filter; the default (no\n\t// override) is the same `[url, ...reference]` as before. Built-in redirect handling still reads\n\t// `redirect.url`/`redirect.reference`, so a host replacing `url` owns resolving their field to a\n\t// destination in their frontend.\n\tconst urlField: Field = {\n\t\tname: 'url',\n\t\ttype: 'text',\n\t\tlabel: labelForKey(keys.responseUrl),\n\t\tvalidate: validateUrl,\n\t}\n\tconst defaultRedirectFields: Field[] = [urlField, ...redirectReferenceField]\n\tconst redirectFields = response?.redirect?.fields\n\t\t? response.redirect.fields({ defaultFields: defaultRedirectFields })\n\t\t: defaultRedirectFields\n\n\t// What the visitor sees after a successful submit. Publicly readable (unlike actions): the\n\t// client renderer needs message/redirect. `type`/`url` are behavior, never localized;\n\t// `message` is visitor-facing content and follows `localizeContent`.\n\t// `type` is defaulted and not clearable rather than `required`: a required member would make\n\t// the whole group required in generated types, breaking typed `payload.create` calls that\n\t// omit `response`. Consumers treat a missing type as 'message'.\n\tconst responseField: Field = {\n\t\tname: 'response',\n\t\ttype: 'group',\n\t\tfields: [\n\t\t\t{\n\t\t\t\tname: 'type',\n\t\t\t\ttype: 'select',\n\t\t\t\tdefaultValue: 'message',\n\t\t\t\tlabel: labelForKey(keys.responseType),\n\t\t\t\tadmin: { isClearable: false },\n\t\t\t\toptions: [\n\t\t\t\t\t{ label: labelForKey(keys.responseTypeMessage), value: 'message' },\n\t\t\t\t\t{ label: labelForKey(keys.responseTypeRedirect), value: 'redirect' },\n\t\t\t\t],\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'message',\n\t\t\t\ttype: 'richText',\n\t\t\t\tlabel: labelForKey(keys.responseMessage),\n\t\t\t\t// Unset type (docs predating this field) means 'message', matching the client fallback.\n\t\t\t\tadmin: { condition: (_data, siblingData) => siblingData?.type !== 'redirect' },\n\t\t\t\t...(responseEditor ? { editor: responseEditor } : {}),\n\t\t\t\t...localizedIf(localizeContent),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'redirect',\n\t\t\t\ttype: 'group',\n\t\t\t\tlabel: labelForKey(keys.responseRedirect),\n\t\t\t\tadmin: {\n\t\t\t\t\tcondition: (_data, siblingData) => siblingData?.type === 'redirect',\n\t\t\t\t\thideGutter: true,\n\t\t\t\t},\n\t\t\t\tfields: redirectFields,\n\t\t\t},\n\t\t],\n\t}\n\n\tconst defaultOutcomeFields = buildDefaultOutcomeFields()\n\tconst defaultButtonFields = buildDefaultButtonFields(localizeContent)\n\t// Half-width copy of a default field for the prev/next row; merges width into whatever admin the\n\t// field already carries (a host's `buttons.fields` override may add its own) rather than replacing\n\t// it. The cast is safe: `width` is a valid admin option on every field variant, but spreading a\n\t// `Field`-typed value's `admin` back into a union-typed object literal loses the discriminant TS\n\t// needs to check it structurally.\n\tconst halfWidth = (field: Field): Field =>\n\t\t({ ...field, admin: { ...field.admin, width: '50%' } }) as Field\n\t// The three button-label fields sit at the document root now (no `buttons` group): `submit` at\n\t// the bottom of the Fields tab, `prev`/`next` in a row on the Flow tab. The `buttons.fields` seam\n\t// composes each slot from the already-localized defaults, so a host can wrap a default in a row\n\t// with its own field (e.g. an icon select) or replace it; `toFormDocument` reassembles the three\n\t// labels into `FormDocument.buttons` for the client.\n\tconst composedButtons = buttons?.fields\n\t\t? buttons.fields({ defaultFields: defaultButtonFields })\n\t\t: defaultButtonFields\n\tconst submitField = composedButtons.submit\n\tconst prevNextRow: Field = {\n\t\ttype: 'row',\n\t\t// The prev/next labels only matter once the flow has a step; the Flow tab itself is gated on\n\t\t// `multistep`. `data` (1st arg) is the whole document, so `flow.steps` reads the stored flow.\n\t\tadmin: {\n\t\t\tcondition: (data) => {\n\t\t\t\tconst steps = (data as { flow?: { steps?: unknown } })?.flow?.steps\n\t\t\t\treturn Array.isArray(steps) && steps.length >= 1\n\t\t\t},\n\t\t},\n\t\tfields: [halfWidth(composedButtons.prev), halfWidth(composedButtons.next)],\n\t}\n\n\t// Poll lifecycle config; identifiers and behavior, never localized. Lives inside the conditional\n\t// Poll tab (gated on the top-level `pollEnabled` flag), so the per-field enabled conditions are\n\t// gone; `label: false` suppresses the group header the tab label already provides.\n\t// `resultsVisibility` is defaulted and not clearable rather than `required` for the same\n\t// generated-types reason as `response.type`.\n\tconst pollGroupField: Field = {\n\t\tname: 'poll',\n\t\ttype: 'group',\n\t\tlabel: false,\n\t\tfields: [\n\t\t\t// Authored by picking from the form's poll-eligible fields; the stored value stays a plain\n\t\t\t// text field name. Select options and server validate share `pollResultsTypes`, so they\n\t\t\t// cannot drift.\n\t\t\t{\n\t\t\t\tname: 'resultsField',\n\t\t\t\ttype: 'text',\n\t\t\t\tlabel: labelForKey(keys.pollResultsField),\n\t\t\t\tvalidate: buildValidateResultsField(pollResultsTypes),\n\t\t\t\tadmin: {\n\t\t\t\t\tcomponents: {\n\t\t\t\t\t\tField: {\n\t\t\t\t\t\t\tpath: FIELD_NAME_SELECT_REF,\n\t\t\t\t\t\t\tclientProps: {\n\t\t\t\t\t\t\t\ttypes: pollResultsTypes,\n\t\t\t\t\t\t\t\tdescriptionKey: keys.pollResultsFieldDescription,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t// How the winning value(s) get decided. `mostVoted` (default) and `source` auto-resolve on close\n\t\t\t// (via the scheduled job or the results-read fallback); `manual` leaves it to an admin. Options\n\t\t\t// come from the registered `poll.types` strategies, so a host strategy appears here too, with\n\t\t\t// `mostVoted` first so an enabled poll works with no winner to hand-pick.\n\t\t\t{\n\t\t\t\tname: 'type',\n\t\t\t\ttype: 'select',\n\t\t\t\tdefaultValue: 'mostVoted',\n\t\t\t\tlabel: labelForKey(keys.pollType),\n\t\t\t\tadmin: { isClearable: false, description: labelForKey(keys.pollTypeDescription) },\n\t\t\t\toptions: orderedPollTypes\n\t\t\t\t\t.filter((strategy) => strategy.type !== 'source' || hasOptionSources)\n\t\t\t\t\t.map((strategy) => ({\n\t\t\t\t\t\tlabel: resolveDefinitionLabel(strategy.label),\n\t\t\t\t\t\tvalue: strategy.type,\n\t\t\t\t\t})),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'resultsVisibility',\n\t\t\t\ttype: 'select',\n\t\t\t\tdefaultValue: 'afterVote',\n\t\t\t\tlabel: labelForKey(keys.pollResultsVisibility),\n\t\t\t\tadmin: { isClearable: false },\n\t\t\t\toptions: [\n\t\t\t\t\t{ label: labelForKey(keys.pollVisibilityAfterVote), value: 'afterVote' },\n\t\t\t\t\t{ label: labelForKey(keys.pollVisibilityAfterClose), value: 'afterClose' },\n\t\t\t\t],\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'allowChange',\n\t\t\t\ttype: 'checkbox',\n\t\t\t\tdefaultValue: false,\n\t\t\t\tlabel: labelForKey(keys.pollAllowChange),\n\t\t\t\tadmin: { description: labelForKey(keys.pollAllowChangeDescription) },\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'closesAt',\n\t\t\t\ttype: 'date',\n\t\t\t\tlabel: labelForKey(keys.pollClosesAt),\n\t\t\t\tadmin: { date: { pickerAppearance: 'dayAndTime' } },\n\t\t\t},\n\t\t\t// Close / reopen the poll from the admin. The button toggles on the live `closesAt` and saves the\n\t\t\t// whole document (persisting an unsaved winner alongside `closesAt`) rather than calling the close\n\t\t\t// endpoint, so there is no DB-vs-form-state mismatch. Always mounted inside the (pollEnabled-gated)\n\t\t\t// Poll tab now; the button owns both states, so it carries no `admin.condition` of its own.\n\t\t\t{\n\t\t\t\tname: 'closePoll',\n\t\t\t\ttype: 'ui',\n\t\t\t\tadmin: {\n\t\t\t\t\tcomponents: { Field: CLOSE_POLL_BUTTON_REF },\n\t\t\t\t},\n\t\t\t},\n\t\t\t...buildPollOptionSourceFields(pollSourceRegistry ?? new Map()),\n\t\t\t// `winningValues` is recorded either by an admin picking from the poll's effective options\n\t\t\t// (served by `/:id/poll-options`) or by `resolvePollOutcome` (host domain logic); more than\n\t\t\t// one value records a tie. The `pollOutcomeBeforeChange` hook validates both paths and owns\n\t\t\t// the `resolvedAt` stamp. `resolvedAt` itself stays fully locked: field-level create/update\n\t\t\t// access blocks every non-override caller write (Payload silently drops the denied value\n\t\t\t// rather than erroring) while the hook's stamp, applied after access filtering, still\n\t\t\t// persists. The `poll.outcomeFields` seam receives both defaults and its return becomes the\n\t\t\t// group's fields verbatim, so a host can swap `winningValues` for its own component; the hook\n\t\t\t// still validates every stored value against the effective options, so no swap bypasses it.\n\t\t\t// `hideGutter` stays: outcome is still a group nested inside the poll group.\n\t\t\t{\n\t\t\t\tname: 'outcome',\n\t\t\t\ttype: 'group',\n\t\t\t\tlabel: labelForKey(keys.pollOutcome),\n\t\t\t\tadmin: { hideGutter: true },\n\t\t\t\tfields: outcomeFields\n\t\t\t\t\t? outcomeFields({ defaultFields: defaultOutcomeFields })\n\t\t\t\t\t: [defaultOutcomeFields.winningValues, defaultOutcomeFields.resolvedAt],\n\t\t\t},\n\t\t],\n\t}\n\n\tconst defaultFields: Field[] = [\n\t\t// The document title is the only visitor-facing text the collection renders itself: it is\n\t\t// always required (drives `useAsTitle` in the admin list/relationship views) and is passed\n\t\t// through `toFormDocument` as `FormDocument.title`. Whether and how a host renders it above\n\t\t// the fields is entirely the host's call; the plugin does not gate or duplicate it.\n\t\t{\n\t\t\tname: 'title',\n\t\t\ttype: 'text',\n\t\t\tlabel: labelForKey(keys.fieldTitle),\n\t\t\t// Visitor-facing content, so it follows `localizeContent` like the response message and\n\t\t\t// consent statement: hosts with localization get a per-locale title (and a locale-aware\n\t\t\t// `useAsTitle`), hosts without it are unaffected since Payload strips the flag. `validate`\n\t\t\t// (not `required`) keeps it mandatory only in the default locale so other locales fall back.\n\t\t\t...localizedIf(localizeContent),\n\t\t\tvalidate: validateFormTitle,\n\t\t},\n\t\t// The three form-level flags: behavior, never localized, sidebar checkboxes by default.\n\t\t// `multistep` gates the Flow tab and the client's step navigation; `pollEnabled` gates the\n\t\t// Poll tab and marks the form a poll; `persistSubmissions` (default checked) tells the plugin\n\t\t// whether to keep a submission's row after its actions run, or prune it. The `settings.fields`\n\t\t// seam composes them from the defaults, mirroring `buttons.fields`.\n\t\t...composeSettingsFields(settings),\n\t\t// Unnamed tabs are presentational: their fields stay at the document root. An unnamed tab's\n\t\t// `admin.condition` receives the whole document as its 2nd arg, so the Flow and Poll tabs gate\n\t\t// on the root-level flags. The poll group nests its config under `form.poll` as before.\n\t\t{\n\t\t\ttype: 'tabs',\n\t\t\ttabs: [\n\t\t\t\t{ label: labelForKey(keys.tabFields), fields: [fieldsField, submitField] },\n\t\t\t\t{\n\t\t\t\t\tlabel: labelForKey(keys.tabFlow),\n\t\t\t\t\tadmin: { condition: (_data, siblingData) => siblingData?.multistep === true },\n\t\t\t\t\tfields: [flowField, prevNextRow],\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: labelForKey(keys.pollGroup),\n\t\t\t\t\tadmin: { condition: (_data, siblingData) => siblingData?.pollEnabled === true },\n\t\t\t\t\tfields: [pollGroupField],\n\t\t\t\t},\n\t\t\t\t{ label: labelForKey(keys.tabActions), fields: [actionsField] },\n\t\t\t\t{ label: labelForKey(keys.tabResponse), fields: [responseField] },\n\t\t\t],\n\t\t},\n\t]\n\n\t// When `consent.sources` is set, resolve the visitor-facing consent statements onto every read of\n\t// a form doc (REST, local API, relationship population), so a client/modal fetch renders consent,\n\t// not only the RSC path that calls `resolveConsentStatements` itself. Fails open: a resolver outage\n\t// must never break form/admin/relationship reads, and the renderer already tolerates a missing\n\t// statement. `resolveConsentStatements` returns `{}` without calling the resolver when the form has\n\t// no consent fields, and entries are cached per request+form, so the cost stays bounded.\n\tconst consentAfterRead: CollectionAfterReadHook | undefined =\n\t\tconsentSources && consentResolveOnRead !== false\n\t\t\t? async ({ doc, req }) => {\n\t\t\t\t\tconst id = (doc as { id?: unknown }).id\n\t\t\t\t\tconst key = id == null ? undefined : String(id)\n\t\t\t\t\t// Re-entrance guard: a host resolver that reads this same form back (threading req) would\n\t\t\t\t\t// otherwise re-enter this hook and recurse. Payload runs collection afterRead concurrently\n\t\t\t\t\t// across list docs on one shared req.context, so this must be a per-id Set (a boolean would\n\t\t\t\t\t// make sibling docs skip), mutated in place and never reassigned after fan-out. Mirrors\n\t\t\t\t\t// @payloadcms/plugin-search's syncDocAsSearchIndex guard.\n\t\t\t\t\tif (key !== undefined && req.context) {\n\t\t\t\t\t\tconst inFlight =\n\t\t\t\t\t\t\t(req.context[CONSENT_AFTER_READ_GUARD] as Set<string> | undefined) ??\n\t\t\t\t\t\t\tnew Set<string>()\n\t\t\t\t\t\tif (inFlight.has(key)) {\n\t\t\t\t\t\t\treturn doc\n\t\t\t\t\t\t}\n\t\t\t\t\t\tinFlight.add(key)\n\t\t\t\t\t\treq.context[CONSENT_AFTER_READ_GUARD] = inFlight\n\t\t\t\t\t}\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst statements = await resolveConsentStatements({\n\t\t\t\t\t\t\tpayload: req.payload,\n\t\t\t\t\t\t\treq,\n\t\t\t\t\t\t\tform: doc,\n\t\t\t\t\t\t\tsources: consentSources,\n\t\t\t\t\t\t})\n\t\t\t\t\t\tif (Object.keys(statements).length > 0) {\n\t\t\t\t\t\t\t;(doc as Record<string, unknown>).consentStatements = statements\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\treq.payload.logger?.warn(`form-builder consent afterRead: ${String(error)}`)\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tif (key !== undefined && req.context) {\n\t\t\t\t\t\t\t;(req.context[CONSENT_AFTER_READ_GUARD] as Set<string> | undefined)?.delete(key)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn doc\n\t\t\t\t}\n\t\t\t: undefined\n\n\t// When calc sources are registered, resolve the values a form's expressions reference onto every\n\t// read of its doc, stamped as `doc.calcResolved` (`toFormDocument` passes it through), so the\n\t// client's live preview computes with real values. Fails open: a resolver outage must never break\n\t// form/admin/relationship reads; the preview degrades to 0 and submit re-resolves loudly.\n\tconst calcAfterRead: CollectionAfterReadHook | undefined =\n\t\tcalcSources && Object.keys(calcSources).length > 0\n\t\t\t? async ({ doc, req }) => {\n\t\t\t\t\tconst fields = Array.isArray((doc as { fields?: unknown }).fields)\n\t\t\t\t\t\t? ((doc as { fields: unknown[] }).fields as FormFieldInstance[])\n\t\t\t\t\t\t: []\n\t\t\t\t\tif (!calcUsesSources(fields)) {\n\t\t\t\t\t\treturn doc\n\t\t\t\t\t}\n\t\t\t\t\tconst id = (doc as { id?: unknown }).id\n\t\t\t\t\tconst key = id == null ? undefined : String(id)\n\t\t\t\t\t// Re-entrance guard: a host resolver that reads this same form back (threading req) would\n\t\t\t\t\t// otherwise re-enter this hook and recurse. Per-id Set, mutated in place, exactly like\n\t\t\t\t\t// `consentAfterRead` above (afterRead fans out concurrently over one shared req.context).\n\t\t\t\t\tif (key !== undefined && req.context) {\n\t\t\t\t\t\tconst inFlight =\n\t\t\t\t\t\t\t(req.context[CALC_AFTER_READ_GUARD] as Set<string> | undefined) ?? new Set<string>()\n\t\t\t\t\t\tif (inFlight.has(key)) {\n\t\t\t\t\t\t\treturn doc\n\t\t\t\t\t\t}\n\t\t\t\t\t\tinFlight.add(key)\n\t\t\t\t\t\treq.context[CALC_AFTER_READ_GUARD] = inFlight\n\t\t\t\t\t}\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst resolved = await resolveCalcContext({\n\t\t\t\t\t\t\tfields,\n\t\t\t\t\t\t\tsources: calcSources,\n\t\t\t\t\t\t\tform: doc as { id: number | string } & Record<string, unknown>,\n\t\t\t\t\t\t\tpayload: req.payload,\n\t\t\t\t\t\t\treq,\n\t\t\t\t\t\t})\n\t\t\t\t\t\t;(doc as Record<string, unknown>).calcResolved = resolved\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\treq.payload.logger?.warn(`form-builder calc afterRead: ${String(error)}`)\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tif (key !== undefined && req.context) {\n\t\t\t\t\t\t\t;(req.context[CALC_AFTER_READ_GUARD] as Set<string> | undefined)?.delete(key)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn doc\n\t\t\t\t}\n\t\t\t: undefined\n\n\tconst defaultEndpoints = buildFormsEndpoints({\n\t\tresultsAccess,\n\t\tpollResultsTypes,\n\t\tpollVotesEnabled,\n\t\tconsentSources,\n\t\tfromAddresses,\n\t\tfromSources,\n\t\tdepartments,\n\t})\n\n\treturn {\n\t\t...(overrides ?? {}),\n\t\tslug: slugs.forms,\n\t\tlabels: {\n\t\t\tsingular: labelForKey(keys.collectionFormSingular),\n\t\t\tplural: labelForKey(keys.collectionFormPlural),\n\t\t\t...(overrides?.labels ?? {}),\n\t\t},\n\t\tadmin: {\n\t\t\tgroup: 'Forms',\n\t\t\tuseAsTitle: 'title',\n\t\t\tdefaultColumns: ['title', 'fields', 'flow', 'pollEnabled', 'updatedAt'],\n\t\t\t...(overrides?.admin ?? {}),\n\t\t},\n\t\taccess: { read: () => true, ...(overrides?.access ?? {}) },\n\t\thooks: {\n\t\t\t...(overrides?.hooks ?? {}),\n\t\t\t// beforeValidate normalizes conditions and flow; consumer hooks run after\n\t\t\tbeforeValidate: [\n\t\t\t\tbeforeValidate,\n\t\t\t\tactionConfigBeforeValidate,\n\t\t\t\t...(overrides?.hooks?.beforeValidate ?? []),\n\t\t\t],\n\t\t\tbeforeChange: [pollOutcomeBeforeChange, ...(overrides?.hooks?.beforeChange ?? [])],\n\t\t\tafterChange: [pollCloseAfterChange, ...(overrides?.hooks?.afterChange ?? [])],\n\t\t\tafterRead: [\n\t\t\t\t...(consentAfterRead ? [consentAfterRead] : []),\n\t\t\t\t...(calcAfterRead ? [calcAfterRead] : []),\n\t\t\t\t...(overrides?.hooks?.afterRead ?? []),\n\t\t\t],\n\t\t},\n\t\tendpoints: [\n\t\t\t...defaultEndpoints,\n\t\t\t...(Array.isArray(overrides?.endpoints) ? overrides.endpoints : []),\n\t\t],\n\t\tfields: overrides?.fields ? overrides.fields({ defaultFields }) : defaultFields,\n\t}\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { resolveConsentEntries } from "./resolveConsentEntries.js";
2
- import { FORMS_SLUG } from "../collections/forms.js";
2
+ import { pluginSlugsOf } from "../plugin/collectionSlugs.js";
3
3
  //#region src/consent/resolveConsentSourcesRequest.ts
4
4
  /**
5
5
  * Authorize and resolve the `GET /:id/consent-sources` request backing the consent `source` select:
@@ -19,7 +19,7 @@ const resolveConsentSourcesRequest = async (args) => {
19
19
  body: { errors: [{ message: "Missing form id" }] }
20
20
  };
21
21
  const form = await payload.findByID({
22
- collection: FORMS_SLUG,
22
+ collection: pluginSlugsOf(payload).forms,
23
23
  id: formId,
24
24
  depth: 0,
25
25
  req
@@ -1 +1 @@
1
- {"version":3,"file":"resolveConsentSourcesRequest.js","names":[],"sources":["../../src/consent/resolveConsentSourcesRequest.ts"],"sourcesContent":["import type { Payload, PayloadRequest } from 'payload'\nimport { FORMS_SLUG } from '../collections/forms'\nimport { resolveConsentEntries } from './resolveConsentEntries'\nimport type { ConsentSourcesResolver } from './types'\n\n/** One selectable consent source for the consent field's `source` select. */\nexport type ConsentSourceOption = { label: string; value: string }\n\nexport type ResolveConsentSourcesRequestArgs = {\n\tpayload: Payload\n\tformId: number | string | undefined\n\t/** Whether the caller is authenticated (an admin/user). */\n\tisAuthed: boolean\n\treq: PayloadRequest\n\tresolver: ConsentSourcesResolver\n}\n\nexport type ResolveConsentSourcesRequestResult = {\n\tstatus: number\n\tbody: { options: ConsentSourceOption[] } | { errors: { message: string }[] }\n}\n\n/**\n * Authorize and resolve the `GET /:id/consent-sources` request backing the consent `source` select:\n * authenticated callers get the host's sources for this request (a multi-tenant resolver scoping by\n * `req` therefore offers an author only their own tenant's), anonymous callers are always refused.\n * Statuses mirror the poll-options endpoint: 400 missing id, 403 unauthenticated, 404 unknown form,\n * 503 when the resolver throws (fail closed).\n */\nexport const resolveConsentSourcesRequest = async (\n\targs: ResolveConsentSourcesRequestArgs\n): Promise<ResolveConsentSourcesRequestResult> => {\n\tconst { payload, formId, isAuthed, req, resolver } = args\n\tif (!isAuthed) {\n\t\treturn { status: 403, body: { errors: [{ message: 'Forbidden' }] } }\n\t}\n\tif (formId == null) {\n\t\treturn { status: 400, body: { errors: [{ message: 'Missing form id' }] } }\n\t}\n\t// Load under the caller's own read access (no overrideAccess): a tenant-scoped `forms.access.read`\n\t// then refuses a cross-tenant form id (null -> 404), so an authed user can never enumerate another\n\t// tenant's consent source labels/ids.\n\tconst form = await payload\n\t\t.findByID({ collection: FORMS_SLUG, id: formId, depth: 0, req })\n\t\t.catch(() => null)\n\tif (!form) {\n\t\treturn { status: 404, body: { errors: [{ message: 'Not found' }] } }\n\t}\n\ttry {\n\t\tconst entries = await resolveConsentEntries({ payload, req, form, sources: resolver })\n\t\treturn {\n\t\t\tstatus: 200,\n\t\t\tbody: {\n\t\t\t\toptions: entries.map((entry) => ({ label: entry.name ?? entry.id, value: entry.id })),\n\t\t\t},\n\t\t}\n\t} catch {\n\t\treturn { status: 503, body: { errors: [{ message: 'Consent sources unavailable' }] } }\n\t}\n}\n"],"mappings":";;;;;;;;;;AA6BA,MAAa,+BAA+B,OAC3C,SACiD;CACjD,MAAM,EAAE,SAAS,QAAQ,UAAU,KAAK,aAAa;CACrD,IAAI,CAAC,UACJ,OAAO;EAAE,QAAQ;EAAK,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,YAAY,CAAC,EAAE;CAAE;CAEpE,IAAI,UAAU,MACb,OAAO;EAAE,QAAQ;EAAK,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,kBAAkB,CAAC,EAAE;CAAE;CAK1E,MAAM,OAAO,MAAM,QACjB,SAAS;EAAE,YAAY;EAAY,IAAI;EAAQ,OAAO;EAAG;CAAI,CAAC,EAC9D,YAAY,IAAI;CAClB,IAAI,CAAC,MACJ,OAAO;EAAE,QAAQ;EAAK,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,YAAY,CAAC,EAAE;CAAE;CAEpE,IAAI;EAEH,OAAO;GACN,QAAQ;GACR,MAAM,EACL,UAAS,MAJW,sBAAsB;IAAE;IAAS;IAAK;IAAM,SAAS;GAAS,CAAC,GAIlE,KAAK,WAAW;IAAE,OAAO,MAAM,QAAQ,MAAM;IAAI,OAAO,MAAM;GAAG,EAAE,EACrF;EACD;CACD,QAAQ;EACP,OAAO;GAAE,QAAQ;GAAK,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,8BAA8B,CAAC,EAAE;EAAE;CACtF;AACD"}
1
+ {"version":3,"file":"resolveConsentSourcesRequest.js","names":[],"sources":["../../src/consent/resolveConsentSourcesRequest.ts"],"sourcesContent":["import type { Payload, PayloadRequest } from 'payload'\nimport { pluginSlugsOf } from '../plugin/collectionSlugs'\nimport { resolveConsentEntries } from './resolveConsentEntries'\nimport type { ConsentSourcesResolver } from './types'\n\n/** One selectable consent source for the consent field's `source` select. */\nexport type ConsentSourceOption = { label: string; value: string }\n\nexport type ResolveConsentSourcesRequestArgs = {\n\tpayload: Payload\n\tformId: number | string | undefined\n\t/** Whether the caller is authenticated (an admin/user). */\n\tisAuthed: boolean\n\treq: PayloadRequest\n\tresolver: ConsentSourcesResolver\n}\n\nexport type ResolveConsentSourcesRequestResult = {\n\tstatus: number\n\tbody: { options: ConsentSourceOption[] } | { errors: { message: string }[] }\n}\n\n/**\n * Authorize and resolve the `GET /:id/consent-sources` request backing the consent `source` select:\n * authenticated callers get the host's sources for this request (a multi-tenant resolver scoping by\n * `req` therefore offers an author only their own tenant's), anonymous callers are always refused.\n * Statuses mirror the poll-options endpoint: 400 missing id, 403 unauthenticated, 404 unknown form,\n * 503 when the resolver throws (fail closed).\n */\nexport const resolveConsentSourcesRequest = async (\n\targs: ResolveConsentSourcesRequestArgs\n): Promise<ResolveConsentSourcesRequestResult> => {\n\tconst { payload, formId, isAuthed, req, resolver } = args\n\tif (!isAuthed) {\n\t\treturn { status: 403, body: { errors: [{ message: 'Forbidden' }] } }\n\t}\n\tif (formId == null) {\n\t\treturn { status: 400, body: { errors: [{ message: 'Missing form id' }] } }\n\t}\n\t// Load under the caller's own read access (no overrideAccess): a tenant-scoped `forms.access.read`\n\t// then refuses a cross-tenant form id (null -> 404), so an authed user can never enumerate another\n\t// tenant's consent source labels/ids.\n\tconst form = await payload\n\t\t.findByID({ collection: pluginSlugsOf(payload).forms, id: formId, depth: 0, req })\n\t\t.catch(() => null)\n\tif (!form) {\n\t\treturn { status: 404, body: { errors: [{ message: 'Not found' }] } }\n\t}\n\ttry {\n\t\tconst entries = await resolveConsentEntries({ payload, req, form, sources: resolver })\n\t\treturn {\n\t\t\tstatus: 200,\n\t\t\tbody: {\n\t\t\t\toptions: entries.map((entry) => ({ label: entry.name ?? entry.id, value: entry.id })),\n\t\t\t},\n\t\t}\n\t} catch {\n\t\treturn { status: 503, body: { errors: [{ message: 'Consent sources unavailable' }] } }\n\t}\n}\n"],"mappings":";;;;;;;;;;AA6BA,MAAa,+BAA+B,OAC3C,SACiD;CACjD,MAAM,EAAE,SAAS,QAAQ,UAAU,KAAK,aAAa;CACrD,IAAI,CAAC,UACJ,OAAO;EAAE,QAAQ;EAAK,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,YAAY,CAAC,EAAE;CAAE;CAEpE,IAAI,UAAU,MACb,OAAO;EAAE,QAAQ;EAAK,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,kBAAkB,CAAC,EAAE;CAAE;CAK1E,MAAM,OAAO,MAAM,QACjB,SAAS;EAAE,YAAY,cAAc,OAAO,EAAE;EAAO,IAAI;EAAQ,OAAO;EAAG;CAAI,CAAC,EAChF,YAAY,IAAI;CAClB,IAAI,CAAC,MACJ,OAAO;EAAE,QAAQ;EAAK,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,YAAY,CAAC,EAAE;CAAE;CAEpE,IAAI;EAEH,OAAO;GACN,QAAQ;GACR,MAAM,EACL,UAAS,MAJW,sBAAsB;IAAE;IAAS;IAAK;IAAM,SAAS;GAAS,CAAC,GAIlE,KAAK,WAAW;IAAE,OAAO,MAAM,QAAQ,MAAM;IAAI,OAAO,MAAM;GAAG,EAAE,EACrF;EACD;CACD,QAAQ;EACP,OAAO;GAAE,QAAQ;GAAK,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,8BAA8B,CAAC,EAAE;EAAE;CACtF;AACD"}
@@ -41,7 +41,7 @@ import { FormPresentation, PresentationWrapperProps } from "../react/presentatio
41
41
  import { PresentationOption, PresentationRegistry, PresentationsConfig, resolvePresentations } from "../react/presentation/registry.js";
42
42
  import { RendererOption, RendererRegistry, RenderersConfig, resolveRenderers } from "../react/registry.js";
43
43
  import { SubmitFormResult, SubmitHandler, submitForm } from "../react/submitForm.js";
44
- import { Form, FormProps, FormSuccessResponse, FormSuccessResult } from "../react/Form.js";
44
+ import { Form, FormCollections, FormProps, FormSuccessResponse, FormSuccessResult } from "../react/Form.js";
45
45
  import { RecallResolver, buildRecallResolver } from "../recall/resolver.js";
46
46
  import { FieldErrors, FormAction, FormState } from "../react/state.js";
47
47
  import { FormContextValue, FormControlLabels, FormStepInfo, useFormContext } from "../react/FormContext.js";
@@ -75,4 +75,4 @@ import { interpolate } from "../recall/interpolate.js";
75
75
  import { CAPTCHA_TOKEN_KEY, DEFAULT_HONEYPOT_FIELD } from "../spam/constants.js";
76
76
  import { makeTranslate } from "../translations/makeTranslate.js";
77
77
  import { formatBytes } from "../uploads/formatBytes.js";
78
- export { type AggregationBucket, type BackButtonRenderProps, Backdrop, type BackdropProps, type BodyContext, type BodyConverter, type BodyConverterArgs, type BodyRender, CAPTCHA_TOKEN_KEY, COUNTRIES, type CalcDisplayConfig, type CalcExpression, type CalcResolved, type CaptchaWidgetHandle, Checkbox, type CheckboxProps, ChoiceGroup, type ChoiceGroupProps, type ChoiceOption, type ConsentStatement, type ConsentStatements, DEFAULT_HONEYPOT_FIELD, DialogSurface, type DialogSurfaceProps, type FetchResultsInput, type FetchResultsResult, type FieldAggregation, type FieldErrors, type FieldRenderer, type FieldRendererProps, FieldShell, type FieldShellProps, type FieldWidth, Form, type FormAction, type FormAdapters, type FormButtonSettings, type FormContextValue, type FormControlLabels, FormControls, type FormControlsProps, type FormDocument, type FormFieldInstance, FormFields, type FormFieldsProps, FormLayout, type FormLayoutProps, type FormPollSettings, type FormPresentation, type FormProps, type FormResponseSettings, FormResults, type FormResultsProps, type FormState, type FormStepInfo, FormSteps, type FormStepsProps, type FormSuccessResponse, type FormSuccessResult, HCAPTCHA_SCRIPT_URL, HcaptchaCaptcha, type HcaptchaCaptchaProps, Honeypot, type HoneypotProps, Input, type InputProps, type LoadScript, type NavigateOptions, type NextButtonRenderProps, Poll, type PollProps, type PrefillOptions, type PresentationOption, type PresentationRegistry, type PresentationWrapperProps, type PresentationsConfig, RECAPTCHA_SCRIPT_URL, type RecallResolver, RecaptchaCaptcha, type RecaptchaCaptchaHandle, type RecaptchaCaptchaProps, type RendererOption, type RendererRegistry, type RendererTranslate, type RenderersConfig, Select, type SelectOption, type SelectProps, type SubmissionValue, type SubmitButtonRenderProps, type SubmitFormResult, type SubmitHandler, TURNSTILE_SCRIPT_URL, Textarea, type TextareaProps, type ToFormDocumentOptions, type TranslationBundle, TurnstileCaptcha, type TurnstileCaptchaProps, US_STATES, type UploadFileInput, type UploadFileResult, type UseDismissOptions, type UseFieldResult, type VoteStorage, type VotedSubmission, ar, buildRecallResolver, bundles, clientRuntimeKeys, cn, computeCalcFields, de, defaultPresentations, defaultRenderers, defineFieldRenderer, en, es, evaluateCalc, evaluateCondition, fetchFormResults, fieldKey, firstStepId, formatBytes, formatCalc, formatCalcValue, fr, id, interpolate, isTerminalStepId, ko, makeTranslate, pt, resolveNextStepId, resolvePresentations, resolveRenderers, ru, sanitizeUrl, serializeBody, stepFieldNames, submitForm, textOfBody, toFormDocument, uk, uploadFile, useCalcValues, useDismiss, useField, useFocusTrap, useFormContext, useFormState, useFormStep, useScrollLock, valuesFromSearchParams, widthProps, zh };
78
+ export { type AggregationBucket, type BackButtonRenderProps, Backdrop, type BackdropProps, type BodyContext, type BodyConverter, type BodyConverterArgs, type BodyRender, CAPTCHA_TOKEN_KEY, COUNTRIES, type CalcDisplayConfig, type CalcExpression, type CalcResolved, type CaptchaWidgetHandle, Checkbox, type CheckboxProps, ChoiceGroup, type ChoiceGroupProps, type ChoiceOption, type ConsentStatement, type ConsentStatements, DEFAULT_HONEYPOT_FIELD, DialogSurface, type DialogSurfaceProps, type FetchResultsInput, type FetchResultsResult, type FieldAggregation, type FieldErrors, type FieldRenderer, type FieldRendererProps, FieldShell, type FieldShellProps, type FieldWidth, Form, type FormAction, type FormAdapters, type FormButtonSettings, type FormCollections, type FormContextValue, type FormControlLabels, FormControls, type FormControlsProps, type FormDocument, type FormFieldInstance, FormFields, type FormFieldsProps, FormLayout, type FormLayoutProps, type FormPollSettings, type FormPresentation, type FormProps, type FormResponseSettings, FormResults, type FormResultsProps, type FormState, type FormStepInfo, FormSteps, type FormStepsProps, type FormSuccessResponse, type FormSuccessResult, HCAPTCHA_SCRIPT_URL, HcaptchaCaptcha, type HcaptchaCaptchaProps, Honeypot, type HoneypotProps, Input, type InputProps, type LoadScript, type NavigateOptions, type NextButtonRenderProps, Poll, type PollProps, type PrefillOptions, type PresentationOption, type PresentationRegistry, type PresentationWrapperProps, type PresentationsConfig, RECAPTCHA_SCRIPT_URL, type RecallResolver, RecaptchaCaptcha, type RecaptchaCaptchaHandle, type RecaptchaCaptchaProps, type RendererOption, type RendererRegistry, type RendererTranslate, type RenderersConfig, Select, type SelectOption, type SelectProps, type SubmissionValue, type SubmitButtonRenderProps, type SubmitFormResult, type SubmitHandler, TURNSTILE_SCRIPT_URL, Textarea, type TextareaProps, type ToFormDocumentOptions, type TranslationBundle, TurnstileCaptcha, type TurnstileCaptchaProps, US_STATES, type UploadFileInput, type UploadFileResult, type UseDismissOptions, type UseFieldResult, type VoteStorage, type VotedSubmission, ar, buildRecallResolver, bundles, clientRuntimeKeys, cn, computeCalcFields, de, defaultPresentations, defaultRenderers, defineFieldRenderer, en, es, evaluateCalc, evaluateCondition, fetchFormResults, fieldKey, firstStepId, formatBytes, formatCalc, formatCalcValue, fr, id, interpolate, isTerminalStepId, ko, makeTranslate, pt, resolveNextStepId, resolvePresentations, resolveRenderers, ru, sanitizeUrl, serializeBody, stepFieldNames, submitForm, textOfBody, toFormDocument, uk, uploadFile, useCalcValues, useDismiss, useField, useFocusTrap, useFormContext, useFormState, useFormStep, useScrollLock, valuesFromSearchParams, widthProps, zh };
@@ -3,12 +3,12 @@ import { interpolate } from "../recall/interpolate.js";
3
3
  import { sanitizeUrl } from "../actions/body/converters.js";
4
4
  import { serializeBody } from "../actions/body/serializeBody.js";
5
5
  import { formatCalcValue } from "../calc/formatCalcValue.js";
6
+ import { CAPTCHA_TOKEN_KEY, DEFAULT_HONEYPOT_FIELD } from "../spam/constants.js";
6
7
  import { fieldKey } from "../fields/fieldKey.js";
7
8
  import { evaluateCalc } from "../calc/evaluate.js";
8
9
  import { computeCalcFields } from "../calc/computeCalcFields.js";
9
- import { textOfBody } from "../actions/body/textOfBody.js";
10
- import { CAPTCHA_TOKEN_KEY, DEFAULT_HONEYPOT_FIELD } from "../spam/constants.js";
11
10
  import { evaluateCondition } from "../conditions/evaluate.js";
11
+ import { textOfBody } from "../actions/body/textOfBody.js";
12
12
  import { ar } from "../translations/ar.js";
13
13
  import { de } from "../translations/de.js";
14
14
  import { en } from "../translations/en.js";
@@ -1,6 +1,7 @@
1
1
  import { FormContextReference, signFormContext, verifyFormContext } from "../context/formContext.js";
2
2
  import { resolveConsentStatements } from "../consent/resolveConsentStatements.js";
3
3
  import { toFormDocument } from "../form/toFormDocument.js";
4
+ import { collectionSlugsOf } from "../plugin/collectionSlugs.js";
4
5
  import { VotedSubmission, resolveVotedSubmission } from "../submissions/resolveVotedSubmission.js";
5
6
  import { isPollClosed } from "../form/pollState.js";
6
7
  import { resolveEffectivePollOptions } from "../poll/effectivePollOptions.js";
@@ -8,4 +9,4 @@ import { resolvePollOptions } from "../poll/resolvePollOptions.js";
8
9
  import { resolvePollOutcome } from "../poll/resolvePollOutcome.js";
9
10
  import { SubmissionAnswers } from "../submissions/SubmissionAnswers.js";
10
11
  import { hasVotedCookie, votedCookieName } from "../submissions/votedCookie.js";
11
- export { type FormContextReference, SubmissionAnswers, type VotedSubmission, hasVotedCookie, isPollClosed, resolveConsentStatements, resolveEffectivePollOptions, resolvePollOptions, resolvePollOutcome, resolveVotedSubmission, signFormContext, toFormDocument, verifyFormContext, votedCookieName };
12
+ export { type FormContextReference, SubmissionAnswers, type VotedSubmission, collectionSlugsOf, hasVotedCookie, isPollClosed, resolveConsentStatements, resolveEffectivePollOptions, resolvePollOptions, resolvePollOutcome, resolveVotedSubmission, signFormContext, toFormDocument, verifyFormContext, votedCookieName };
@@ -1,11 +1,12 @@
1
- import { resolveConsentStatements } from "../consent/resolveConsentStatements.js";
1
+ import { collectionSlugsOf } from "../plugin/collectionSlugs.js";
2
2
  import { isPollClosed } from "../form/pollState.js";
3
3
  import { signFormContext, verifyFormContext } from "../context/formContext.js";
4
4
  import { hasVotedCookie, votedCookieName } from "../submissions/votedCookie.js";
5
5
  import { resolvePollOptions } from "../poll/resolvePollOptions.js";
6
6
  import { resolveEffectivePollOptions } from "../poll/effectivePollOptions.js";
7
+ import { resolveConsentStatements } from "../consent/resolveConsentStatements.js";
7
8
  import { resolvePollOutcome } from "../poll/resolvePollOutcome.js";
8
9
  import { toFormDocument } from "../form/toFormDocument.js";
9
10
  import { resolveVotedSubmission } from "../submissions/resolveVotedSubmission.js";
10
11
  import { SubmissionAnswers } from "../submissions/SubmissionAnswers.js";
11
- export { SubmissionAnswers, hasVotedCookie, isPollClosed, resolveConsentStatements, resolveEffectivePollOptions, resolvePollOptions, resolvePollOutcome, resolveVotedSubmission, signFormContext, toFormDocument, verifyFormContext, votedCookieName };
12
+ export { SubmissionAnswers, collectionSlugsOf, hasVotedCookie, isPollClosed, resolveConsentStatements, resolveEffectivePollOptions, resolvePollOptions, resolvePollOutcome, resolveVotedSubmission, signFormContext, toFormDocument, verifyFormContext, votedCookieName };
@@ -11,6 +11,7 @@ import { FormButtonSettings, FormDocument, FormPollSettings, FormResponseSetting
11
11
  import { FieldTypeOption, FieldTypeRegistry, FieldTypesConfig } from "../fields/registry.js";
12
12
  import { PrefillOptions } from "../prefill/valuesFromSearchParams.js";
13
13
  import { FormEvent, FormEventSink } from "../events/types.js";
14
+ import { FormBuilderCollectionSlugs } from "../plugin/collectionSlugs.js";
14
15
  import { AnyValidationRuleDefinition, ValidationRuleDefinition, ValidationRuleResult } from "../validation/types.js";
15
16
  import { PresentationDensity, PresentationDescriptor, PresentationSurface } from "../presentations/types.js";
16
17
  import { RecallResolver } from "../recall/resolver.js";
@@ -33,4 +34,4 @@ import { CaptchaProvider, CaptchaVerifyArgs, IdentifyFn, RateLimitCheckArgs, Rat
33
34
  import { UploadsOption } from "../plugin/uploadsCollection.js";
34
35
  import { ValidationRuleOption, ValidationRuleRegistry, ValidationRulesConfig } from "../validation/registry.js";
35
36
  import { FormBuilderPluginOptions } from "../options.js";
36
- export type { ActionDefinition, ActionOption, ActionRegistry, ActionResult, ActionRunArgs, ActionValidateArgs, ActionsConfig, AggregateFieldResponsesArgs, AggregateFormResponsesArgs, AggregationBucket, AggregationRow, AnyActionDefinition, AnyFormFieldDefinition, AnyValidationRuleDefinition, ButtonFieldsOverride, ButtonsOption, CalcExpression, CaptchaProvider, CaptchaVerifyArgs, CollectionOverrides, ConsentProof, ConsentSourceEntry, ConsentSourceOption, ConsentSourcePage, ConsentSourcesFieldOptions, ConsentSourcesResolver, ConsentStatement, ConsentStatements, DefaultButtonFields, FieldAggregation, FieldCondition, FieldMeta, FieldTypeOption, FieldTypeRegistry, FieldTypesConfig, FieldsOverride, FileFieldConfig, FileRef, FileRefError, FlowStep, FlowTransition, FormBuilderPluginOptions, FormButtonSettings, FormDocument, FormEvent, FormEventSink, FormFieldDefinition, FormFieldFormat, FormFieldInstance, FormFieldValidate, FormFieldValueKind, FormFlow, FormPollSettings, FormResponseSettings, FormResultsAccess, FormResultsAccessArgs, FromAddressOption, FromAddressesResolver, IdentifyFn, OmittableSharedField, PollOptionSourceOption, PollOptionSourceRegistry, PollOptionSourcesConfig, PrefillOptions, PresentationDensity, PresentationDescriptor, PresentationSurface, RateLimitCheckArgs, RateLimitConfig, RateLimitResult, RateLimiter, RecallResolver, ResolveConsentEntriesArgs, ResolveConsentSourcesRequestArgs, ResolveConsentSourcesRequestResult, ResolveConsentStatementsArgs, ResolvePublishedVersionRefArgs, ResolveResultsRequestArgs, ResolveResultsRequestResult, RichTextBodyOption, SpamConfig, SpamMetadataConfig, SpamOption, SubmissionDescriptor, SubmissionFieldError, SubmissionStatusFilter, SubmissionValue, SubmissionWidth, UploadsOption, ValidationRuleDefinition, ValidationRuleOption, ValidationRuleRegistry, ValidationRuleResult, ValidationRulesConfig };
37
+ export type { ActionDefinition, ActionOption, ActionRegistry, ActionResult, ActionRunArgs, ActionValidateArgs, ActionsConfig, AggregateFieldResponsesArgs, AggregateFormResponsesArgs, AggregationBucket, AggregationRow, AnyActionDefinition, AnyFormFieldDefinition, AnyValidationRuleDefinition, ButtonFieldsOverride, ButtonsOption, CalcExpression, CaptchaProvider, CaptchaVerifyArgs, CollectionOverrides, ConsentProof, ConsentSourceEntry, ConsentSourceOption, ConsentSourcePage, ConsentSourcesFieldOptions, ConsentSourcesResolver, ConsentStatement, ConsentStatements, DefaultButtonFields, FieldAggregation, FieldCondition, FieldMeta, FieldTypeOption, FieldTypeRegistry, FieldTypesConfig, FieldsOverride, FileFieldConfig, FileRef, FileRefError, FlowStep, FlowTransition, FormBuilderCollectionSlugs, FormBuilderPluginOptions, FormButtonSettings, FormDocument, FormEvent, FormEventSink, FormFieldDefinition, FormFieldFormat, FormFieldInstance, FormFieldValidate, FormFieldValueKind, FormFlow, FormPollSettings, FormResponseSettings, FormResultsAccess, FormResultsAccessArgs, FromAddressOption, FromAddressesResolver, IdentifyFn, OmittableSharedField, PollOptionSourceOption, PollOptionSourceRegistry, PollOptionSourcesConfig, PrefillOptions, PresentationDensity, PresentationDescriptor, PresentationSurface, RateLimitCheckArgs, RateLimitConfig, RateLimitResult, RateLimiter, RecallResolver, ResolveConsentEntriesArgs, ResolveConsentSourcesRequestArgs, ResolveConsentSourcesRequestResult, ResolveConsentStatementsArgs, ResolvePublishedVersionRefArgs, ResolveResultsRequestArgs, ResolveResultsRequestResult, RichTextBodyOption, SpamConfig, SpamMetadataConfig, SpamOption, SubmissionDescriptor, SubmissionFieldError, SubmissionStatusFilter, SubmissionValue, SubmissionWidth, UploadsOption, ValidationRuleDefinition, ValidationRuleOption, ValidationRuleRegistry, ValidationRuleResult, ValidationRulesConfig };
@@ -1,5 +1,5 @@
1
1
  import { customStateOf, stashCustomState } from "../plugin/customState.js";
2
- import { FORMS_SLUG } from "../collections/forms.js";
2
+ import { pluginSlugsOf } from "../plugin/collectionSlugs.js";
3
3
  import { sanitizeFallbackLocale } from "payload";
4
4
  //#region src/form/findFormAtLocale.ts
5
5
  const stashFallbackLocale = (custom, fallbackLocale) => stashCustomState(custom, { fallbackLocale });
@@ -40,7 +40,7 @@ const missingFormOnReadError = (error) => {
40
40
  const findFormAtLocale = async ({ payload, id, locale, req, overrideAccess }) => {
41
41
  const { localization } = payload.config;
42
42
  const read = (fallbackLocale) => payload.findByID({
43
- collection: FORMS_SLUG,
43
+ collection: pluginSlugsOf(payload).forms,
44
44
  id,
45
45
  depth: 0,
46
46
  locale,
@@ -1 +1 @@
1
- {"version":3,"file":"findFormAtLocale.js","names":[],"sources":["../../src/form/findFormAtLocale.ts"],"sourcesContent":["import {\n\ttype Payload,\n\ttype PayloadRequest,\n\tsanitizeFallbackLocale,\n\ttype TypedFallbackLocale,\n\ttype TypedLocale,\n} from 'payload'\nimport type { SubmissionForm } from '../actions/submissionContext'\nimport { FORMS_SLUG } from '../collections/forms'\nimport { customStateOf, stashCustomState } from '../plugin/customState'\n\n/**\n * What a `fallbackLocale` resolver returns: a locale code, an ordered list of codes, `false` for no\n * fallback, or `undefined` to keep the default.\n */\nexport type FormFallbackLocaleResult = string | string[] | false | undefined\n\nexport type FormFallbackLocaleArgs = {\n\t/**\n\t * The form as first read at `locale` with the config's own fallback, at depth 0: a non-localized\n\t * owner relationship (a multi-tenant host's `form.tenant`) is already on it, so no read of your own\n\t * is needed. Decide from non-localized fields: a localized one may be empty at `locale`.\n\t */\n\tform: SubmissionForm\n\t/** The locale the form is being read at (the submission's, clamped). */\n\tlocale: string\n\tpayload: Payload\n\treq?: PayloadRequest\n}\n\n/**\n * Host seam choosing the fallback locale per form (plugin option `fallbackLocale`), for hosts where\n * the right fallback depends on the form's owner rather than on the config, e.g. a tenant whose own\n * default locale is not the config-wide one. Runs before the plugin acts on a form server-side\n * (validating a submission, running its actions, serving poll results).\n */\nexport type FormFallbackLocale = (\n\targs: FormFallbackLocaleArgs\n) => Promise<FormFallbackLocaleResult> | FormFallbackLocaleResult\n\ntype FallbackState = { fallbackLocale?: FormFallbackLocale }\n\nexport const stashFallbackLocale = (\n\tcustom: Record<string, unknown> | undefined,\n\tfallbackLocale: FormFallbackLocale\n): Record<string, unknown> => stashCustomState<FallbackState>(custom, { fallbackLocale })\n\nexport type FindFormAtLocaleArgs = {\n\tpayload: Payload\n\tid: number | string\n\t/** A clamped locale (see `resolveSubmissionLocale`), never raw visitor input. */\n\tlocale: string\n\treq?: PayloadRequest\n\toverrideAccess?: boolean\n}\n\n/**\n * A host `fallbackLocale` resolver threw. Kept distinct so callers that read a failed form load as a\n * deleted form (and skip quietly) let this one through instead: a transient failure in host I/O must\n * fail the action run (and retry it on the queued path), never drop it.\n */\nexport class FallbackLocaleError extends Error {\n\tconstructor(cause: unknown) {\n\t\tsuper(\n\t\t\t`@10x-media/form-builder: fallbackLocale resolver failed: ${\n\t\t\t\tcause instanceof Error ? cause.message : String(cause)\n\t\t\t}`,\n\t\t\t{ cause }\n\t\t)\n\t\tthis.name = 'FallbackLocaleError'\n\t}\n}\n\n/**\n * `.catch` handler for callers that treat a form that cannot be read as missing: `null` for a read\n * failure, rethrowing a `FallbackLocaleError`.\n */\nexport const missingFormOnReadError = (error: unknown): null => {\n\tif (error instanceof FallbackLocaleError) {\n\t\tthrow error\n\t}\n\treturn null\n}\n\n/**\n * Loads a form (depth 0) at `locale` for server work done on a visitor's behalf: validating a\n * submission, running its actions, serving poll results. The read falls back exactly like any other\n * Payload read, so with `localization.fallback: false` content the author never filled in at\n * `locale` stays empty, matching what the host's own page renders.\n *\n * With the plugin's `fallbackLocale` resolver set, it gets the form as read and may choose another\n * fallback (the way a host forces one); the form is read again only when that differs from the one\n * already applied.\n *\n * Payload's local API writes `locale` and `fallbackLocale` onto the `req` it is handed. Both are\n * restored afterwards, so a host (or job runner) request passed in comes back unchanged. The restore\n * assumes one operation per `req` at a time: concurrent reads sharing a `req` (a `Promise.all` of\n * creates at different locales) can interleave it, so give each its own request.\n */\nexport const findFormAtLocale = async ({\n\tpayload,\n\tid,\n\tlocale,\n\treq,\n\toverrideAccess,\n}: FindFormAtLocaleArgs) => {\n\tconst { localization } = payload.config\n\tconst read = (fallbackLocale: string | string[] | false | undefined) =>\n\t\tpayload.findByID({\n\t\t\tcollection: FORMS_SLUG,\n\t\t\tid,\n\t\t\tdepth: 0,\n\t\t\t// Cast: a clamped locale is one of the host's own codes, but its concrete `TypedLocale` union\n\t\t\t// is unknowable from the plugin.\n\t\t\tlocale: locale as TypedLocale,\n\t\t\t...(fallbackLocale === undefined ? {} : { fallbackLocale: fallbackLocale as TypedLocale }),\n\t\t\t...(overrideAccess === undefined ? {} : { overrideAccess }),\n\t\t\treq,\n\t\t})\n\tconst previous = req ? { locale: req.locale, fallbackLocale: req.fallbackLocale } : undefined\n\ttry {\n\t\tif (!localization) {\n\t\t\treturn await read(undefined)\n\t\t}\n\t\tconst form = await read(undefined)\n\t\tconst resolver = customStateOf<FallbackState>(payload).fallbackLocale\n\t\tif (!resolver) {\n\t\t\treturn form\n\t\t}\n\t\tlet chosen: FormFallbackLocaleResult\n\t\ttry {\n\t\t\tchosen = await resolver({\n\t\t\t\t// Double cast: a host's generated Form interface has no index signature.\n\t\t\t\tform: form as unknown as SubmissionForm,\n\t\t\t\tlocale,\n\t\t\t\tpayload,\n\t\t\t\treq,\n\t\t\t})\n\t\t} catch (error) {\n\t\t\tthrow new FallbackLocaleError(error)\n\t\t}\n\t\t// Falling back to the read's own locale, or to the fallback Payload already applied (resolved by\n\t\t// Payload's own sanitizer, so the two cannot drift), changes nothing.\n\t\tconst applied = sanitizeFallbackLocale({\n\t\t\tfallbackLocale: undefined as unknown as TypedFallbackLocale,\n\t\t\tlocale,\n\t\t\tlocalization,\n\t\t})\n\t\tif (chosen === undefined || chosen === locale || chosen === applied) {\n\t\t\treturn form\n\t\t}\n\t\treturn await read(chosen)\n\t} finally {\n\t\tif (req && previous) {\n\t\t\treq.locale = previous.locale\n\t\t\treq.fallbackLocale = previous.fallbackLocale\n\t\t}\n\t}\n}\n"],"mappings":";;;;AA0CA,MAAa,uBACZ,QACA,mBAC6B,iBAAgC,QAAQ,EAAE,eAAe,CAAC;;;;;;AAgBxF,IAAa,sBAAb,cAAyC,MAAM;CAC9C,YAAY,OAAgB;EAC3B,MACC,4DACC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,KAEtD,EAAE,MAAM,CACT;EACA,KAAK,OAAO;CACb;AACD;;;;;AAMA,MAAa,0BAA0B,UAAyB;CAC/D,IAAI,iBAAiB,qBACpB,MAAM;CAEP,OAAO;AACR;;;;;;;;;;;;;;;;AAiBA,MAAa,mBAAmB,OAAO,EACtC,SACA,IACA,QACA,KACA,qBAC2B;CAC3B,MAAM,EAAE,iBAAiB,QAAQ;CACjC,MAAM,QAAQ,mBACb,QAAQ,SAAS;EAChB,YAAY;EACZ;EACA,OAAO;EAGC;EACR,GAAI,mBAAmB,KAAA,IAAY,CAAC,IAAI,EAAkB,eAA8B;EACxF,GAAI,mBAAmB,KAAA,IAAY,CAAC,IAAI,EAAE,eAAe;EACzD;CACD,CAAC;CACF,MAAM,WAAW,MAAM;EAAE,QAAQ,IAAI;EAAQ,gBAAgB,IAAI;CAAe,IAAI,KAAA;CACpF,IAAI;EACH,IAAI,CAAC,cACJ,OAAO,MAAM,KAAK,KAAA,CAAS;EAE5B,MAAM,OAAO,MAAM,KAAK,KAAA,CAAS;EACjC,MAAM,WAAW,cAA6B,OAAO,EAAE;EACvD,IAAI,CAAC,UACJ,OAAO;EAER,IAAI;EACJ,IAAI;GACH,SAAS,MAAM,SAAS;IAEjB;IACN;IACA;IACA;GACD,CAAC;EACF,SAAS,OAAO;GACf,MAAM,IAAI,oBAAoB,KAAK;EACpC;EAGA,MAAM,UAAU,uBAAuB;GACtC,gBAAgB,KAAA;GAChB;GACA;EACD,CAAC;EACD,IAAI,WAAW,KAAA,KAAa,WAAW,UAAU,WAAW,SAC3D,OAAO;EAER,OAAO,MAAM,KAAK,MAAM;CACzB,UAAU;EACT,IAAI,OAAO,UAAU;GACpB,IAAI,SAAS,SAAS;GACtB,IAAI,iBAAiB,SAAS;EAC/B;CACD;AACD"}
1
+ {"version":3,"file":"findFormAtLocale.js","names":[],"sources":["../../src/form/findFormAtLocale.ts"],"sourcesContent":["import {\n\ttype Payload,\n\ttype PayloadRequest,\n\tsanitizeFallbackLocale,\n\ttype TypedFallbackLocale,\n\ttype TypedLocale,\n} from 'payload'\nimport type { SubmissionForm } from '../actions/submissionContext'\nimport { pluginSlugsOf } from '../plugin/collectionSlugs'\nimport { customStateOf, stashCustomState } from '../plugin/customState'\n\n/**\n * What a `fallbackLocale` resolver returns: a locale code, an ordered list of codes, `false` for no\n * fallback, or `undefined` to keep the default.\n */\nexport type FormFallbackLocaleResult = string | string[] | false | undefined\n\nexport type FormFallbackLocaleArgs = {\n\t/**\n\t * The form as first read at `locale` with the config's own fallback, at depth 0: a non-localized\n\t * owner relationship (a multi-tenant host's `form.tenant`) is already on it, so no read of your own\n\t * is needed. Decide from non-localized fields: a localized one may be empty at `locale`.\n\t */\n\tform: SubmissionForm\n\t/** The locale the form is being read at (the submission's, clamped). */\n\tlocale: string\n\tpayload: Payload\n\treq?: PayloadRequest\n}\n\n/**\n * Host seam choosing the fallback locale per form (plugin option `fallbackLocale`), for hosts where\n * the right fallback depends on the form's owner rather than on the config, e.g. a tenant whose own\n * default locale is not the config-wide one. Runs before the plugin acts on a form server-side\n * (validating a submission, running its actions, serving poll results).\n */\nexport type FormFallbackLocale = (\n\targs: FormFallbackLocaleArgs\n) => Promise<FormFallbackLocaleResult> | FormFallbackLocaleResult\n\ntype FallbackState = { fallbackLocale?: FormFallbackLocale }\n\nexport const stashFallbackLocale = (\n\tcustom: Record<string, unknown> | undefined,\n\tfallbackLocale: FormFallbackLocale\n): Record<string, unknown> => stashCustomState<FallbackState>(custom, { fallbackLocale })\n\nexport type FindFormAtLocaleArgs = {\n\tpayload: Payload\n\tid: number | string\n\t/** A clamped locale (see `resolveSubmissionLocale`), never raw visitor input. */\n\tlocale: string\n\treq?: PayloadRequest\n\toverrideAccess?: boolean\n}\n\n/**\n * A host `fallbackLocale` resolver threw. Kept distinct so callers that read a failed form load as a\n * deleted form (and skip quietly) let this one through instead: a transient failure in host I/O must\n * fail the action run (and retry it on the queued path), never drop it.\n */\nexport class FallbackLocaleError extends Error {\n\tconstructor(cause: unknown) {\n\t\tsuper(\n\t\t\t`@10x-media/form-builder: fallbackLocale resolver failed: ${\n\t\t\t\tcause instanceof Error ? cause.message : String(cause)\n\t\t\t}`,\n\t\t\t{ cause }\n\t\t)\n\t\tthis.name = 'FallbackLocaleError'\n\t}\n}\n\n/**\n * `.catch` handler for callers that treat a form that cannot be read as missing: `null` for a read\n * failure, rethrowing a `FallbackLocaleError`.\n */\nexport const missingFormOnReadError = (error: unknown): null => {\n\tif (error instanceof FallbackLocaleError) {\n\t\tthrow error\n\t}\n\treturn null\n}\n\n/**\n * Loads a form (depth 0) at `locale` for server work done on a visitor's behalf: validating a\n * submission, running its actions, serving poll results. The read falls back exactly like any other\n * Payload read, so with `localization.fallback: false` content the author never filled in at\n * `locale` stays empty, matching what the host's own page renders.\n *\n * With the plugin's `fallbackLocale` resolver set, it gets the form as read and may choose another\n * fallback (the way a host forces one); the form is read again only when that differs from the one\n * already applied.\n *\n * Payload's local API writes `locale` and `fallbackLocale` onto the `req` it is handed. Both are\n * restored afterwards, so a host (or job runner) request passed in comes back unchanged. The restore\n * assumes one operation per `req` at a time: concurrent reads sharing a `req` (a `Promise.all` of\n * creates at different locales) can interleave it, so give each its own request.\n */\nexport const findFormAtLocale = async ({\n\tpayload,\n\tid,\n\tlocale,\n\treq,\n\toverrideAccess,\n}: FindFormAtLocaleArgs) => {\n\tconst { localization } = payload.config\n\tconst read = (fallbackLocale: string | string[] | false | undefined) =>\n\t\tpayload.findByID({\n\t\t\tcollection: pluginSlugsOf(payload).forms,\n\t\t\tid,\n\t\t\tdepth: 0,\n\t\t\t// Cast: a clamped locale is one of the host's own codes, but its concrete `TypedLocale` union\n\t\t\t// is unknowable from the plugin.\n\t\t\tlocale: locale as TypedLocale,\n\t\t\t...(fallbackLocale === undefined ? {} : { fallbackLocale: fallbackLocale as TypedLocale }),\n\t\t\t...(overrideAccess === undefined ? {} : { overrideAccess }),\n\t\t\treq,\n\t\t})\n\tconst previous = req ? { locale: req.locale, fallbackLocale: req.fallbackLocale } : undefined\n\ttry {\n\t\tif (!localization) {\n\t\t\treturn await read(undefined)\n\t\t}\n\t\tconst form = await read(undefined)\n\t\tconst resolver = customStateOf<FallbackState>(payload).fallbackLocale\n\t\tif (!resolver) {\n\t\t\treturn form\n\t\t}\n\t\tlet chosen: FormFallbackLocaleResult\n\t\ttry {\n\t\t\tchosen = await resolver({\n\t\t\t\t// Double cast: a host's generated Form interface has no index signature.\n\t\t\t\tform: form as unknown as SubmissionForm,\n\t\t\t\tlocale,\n\t\t\t\tpayload,\n\t\t\t\treq,\n\t\t\t})\n\t\t} catch (error) {\n\t\t\tthrow new FallbackLocaleError(error)\n\t\t}\n\t\t// Falling back to the read's own locale, or to the fallback Payload already applied (resolved by\n\t\t// Payload's own sanitizer, so the two cannot drift), changes nothing.\n\t\tconst applied = sanitizeFallbackLocale({\n\t\t\tfallbackLocale: undefined as unknown as TypedFallbackLocale,\n\t\t\tlocale,\n\t\t\tlocalization,\n\t\t})\n\t\tif (chosen === undefined || chosen === locale || chosen === applied) {\n\t\t\treturn form\n\t\t}\n\t\treturn await read(chosen)\n\t} finally {\n\t\tif (req && previous) {\n\t\t\treq.locale = previous.locale\n\t\t\treq.fallbackLocale = previous.fallbackLocale\n\t\t}\n\t}\n}\n"],"mappings":";;;;AA0CA,MAAa,uBACZ,QACA,mBAC6B,iBAAgC,QAAQ,EAAE,eAAe,CAAC;;;;;;AAgBxF,IAAa,sBAAb,cAAyC,MAAM;CAC9C,YAAY,OAAgB;EAC3B,MACC,4DACC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,KAEtD,EAAE,MAAM,CACT;EACA,KAAK,OAAO;CACb;AACD;;;;;AAMA,MAAa,0BAA0B,UAAyB;CAC/D,IAAI,iBAAiB,qBACpB,MAAM;CAEP,OAAO;AACR;;;;;;;;;;;;;;;;AAiBA,MAAa,mBAAmB,OAAO,EACtC,SACA,IACA,QACA,KACA,qBAC2B;CAC3B,MAAM,EAAE,iBAAiB,QAAQ;CACjC,MAAM,QAAQ,mBACb,QAAQ,SAAS;EAChB,YAAY,cAAc,OAAO,EAAE;EACnC;EACA,OAAO;EAGC;EACR,GAAI,mBAAmB,KAAA,IAAY,CAAC,IAAI,EAAkB,eAA8B;EACxF,GAAI,mBAAmB,KAAA,IAAY,CAAC,IAAI,EAAE,eAAe;EACzD;CACD,CAAC;CACF,MAAM,WAAW,MAAM;EAAE,QAAQ,IAAI;EAAQ,gBAAgB,IAAI;CAAe,IAAI,KAAA;CACpF,IAAI;EACH,IAAI,CAAC,cACJ,OAAO,MAAM,KAAK,KAAA,CAAS;EAE5B,MAAM,OAAO,MAAM,KAAK,KAAA,CAAS;EACjC,MAAM,WAAW,cAA6B,OAAO,EAAE;EACvD,IAAI,CAAC,UACJ,OAAO;EAER,IAAI;EACJ,IAAI;GACH,SAAS,MAAM,SAAS;IAEjB;IACN;IACA;IACA;GACD,CAAC;EACF,SAAS,OAAO;GACf,MAAM,IAAI,oBAAoB,KAAK;EACpC;EAGA,MAAM,UAAU,uBAAuB;GACtC,gBAAgB,KAAA;GAChB;GACA;EACD,CAAC;EACD,IAAI,WAAW,KAAA,KAAa,WAAW,UAAU,WAAW,SAC3D,OAAO;EAER,OAAO,MAAM,KAAK,MAAM;CACzB,UAAU;EACT,IAAI,OAAO,UAAU;GACpB,IAAI,SAAS,SAAS;GACtB,IAAI,iBAAiB,SAAS;EAC/B;CACD;AACD"}
package/dist/index.d.ts CHANGED
@@ -21,6 +21,7 @@ import { FormButtonSettings, FormDocument, FormPollSettings, FormResponseSetting
21
21
  import { ToFormDocumentOptions, toFormDocument } from "./form/toFormDocument.js";
22
22
  import { FieldTypeOption, FieldTypeRegistry, FieldTypesConfig } from "./fields/registry.js";
23
23
  import { PrefillOptions, valuesFromSearchParams } from "./prefill/valuesFromSearchParams.js";
24
+ import { DEFAULT_COLLECTION_SLUGS, DEFAULT_VOTED_COOKIE_PREFIX, FormBuilderCollectionSlugs, collectionSlugsOf } from "./plugin/collectionSlugs.js";
24
25
  import { AnyValidationRuleDefinition, ValidationRuleDefinition, ValidationRuleResult } from "./validation/types.js";
25
26
  import { PresentationDensity, PresentationDescriptor, PresentationSurface } from "./presentations/types.js";
26
27
  import { RecallResolver, buildRecallResolver, optionLabelsFor } from "./recall/resolver.js";
@@ -99,5 +100,5 @@ import { FieldTargetParamOptions, fieldTargetParam } from "./validation/fieldTar
99
100
  //#region src/index.d.ts
100
101
  declare const formBuilder: (options: FormBuilderPluginOptions) => import("payload").Plugin;
101
102
  //#endregion
102
- export { type ActionDefinition, ActionError, type ActionOption, type ActionRegistry, type ActionResult, type ActionRunArgs, type ActionValidateArgs, type ActionsConfig, type AggregateFieldResponsesArgs, type AggregateFormResponsesArgs, type AggregationBucket, type AggregationRow, type AnyActionDefinition, type AnyFormFieldDefinition, type AnyPollOptionSource, type AnyValidationRuleDefinition, type BodyContext, type BodyConverter, type BodyConverterArgs, type BodyRender, type ButtonFieldsOverride, type ButtonsOption, CAPTCHA_TOKEN_KEY, type CalcAllowed, type CalcDisplayConfig, type CalcExpression, type CalcFunction, type CalcResolved, type CalcSource, type CalcSourceResolveArgs, type CalcWeightResolveArgs, type CaptchaProvider, type CaptchaVerifyArgs, type ConsentProof, type ConsentSnapshotMode, type ConsentSourceEntry, type ConsentSourceOption, type ConsentSourcePage, type ConsentSourcesFieldOptions, type ConsentSourcesResolver, type ConsentStatement, type ConsentStatements, type CreateSubmissionArgs, type CreatedSubmission, DEFAULT_HONEYPOT_FIELD, DEFAULT_PRESENTATION_NAME, type DefaultButtonFields, type DefaultOutcomeFields, type DefaultSettingsFields, type DepartmentEmailsResolver, type DepartmentOption, type DepartmentsFieldOptions, type EmailActionType, type EmailRender, type EmailRenderArgs, type FieldAggregation, type FieldCondition, type FieldMeta, type FieldTargetParamOptions, type FieldTypeOption, type FieldTypeRegistry, type FieldTypesConfig, type FileFieldConfig, type FileRef, type FileRefError, type FormBuilderPluginOptions, type FormButtonSettings, type FormContextReference, type FormDocument, type FormFallbackLocale, type FormFallbackLocaleArgs, type FormFallbackLocaleResult, type FormFieldDefinition, type FormFieldFormat, type FormFieldValidate, type FormFieldValueKind, type FormPollSettings, type FormResponseSettings, type FormResultsAccess, type FormResultsAccessArgs, type FromAddressOption, type FromAddressSource, type FromAddressSourceRegistry, type FromAddressesResolver, type HcaptchaProviderOptions, INLINE_DISPATCH_DEADLINE_MS, type IdentifyFn, type OmittableSharedField, type OutcomeFieldsOverride, POLL_CLOSE_TASK_SLUG, POLL_VOTES_SLUG, type FormBuilderPluginOptions as PluginOptions, type PollCloseTaskInput, type PollOption, type PollOptionResolveArgs, type PollOptionSource, type PollOptionSourceOption, type PollOptionSourceRegistry, type PollOptionSourcesConfig, type PollOutcomeStrategy, type PollOutcomeStrategyArgs, type PollTypeRegistry, type PollTypesConfig, type PrefillOptions, type PresentationDensity, type PresentationDescriptor, type PresentationSurface, RESPONDENTS_VALUE, type RateLimitCheckArgs, type RateLimitConfig, type RateLimitResult, type RateLimiter, type RecallResolver, type RecaptchaProviderOptions, type RecipientResolveArgs, type RecipientSource, type RecipientSourceRegistry, type RedirectFieldsOverride, type RedirectOption, type ResolveCalcContextArgs, type ResolveConsentEntriesArgs, type ResolveConsentSourcesRequestArgs, type ResolveConsentSourcesRequestResult, type ResolveConsentStatementsArgs, type ResolveDepartmentOptionsArgs, type ResolveDepartmentsRequestArgs, type ResolveDepartmentsRequestResult, type ResolveEffectivePollOptionsArgs, type ResolveFieldOptionsArgs, type ResolvePollOptionsArgs, type ResolvePollOutcomeArgs, type ResolvePublishedVersionRefArgs, type ResolveResultsRequestArgs, type ResolveResultsRequestResult, type ResponseOption, type RichTextBodyOption, SIGNATURE_HEADER, type SerializeBodyArgs, type SettingsFieldsOverride, type SettingsOption, type SpamConfig, type SpamMetadataConfig, type SpamOption, type SubmissionContextArgs, type SubmissionForm, type SubmissionStatusFilter, type ToFormDocumentOptions, type TurnstileProviderOptions, type UploadsOption, VOTE_SHARDS, type ValidationRuleDefinition, type ValidationRuleOption, type ValidationRuleRegistry, type ValidationRuleResult, type ValidationRulesConfig, type VotedSubmission, aggregateFieldResponses, aggregateFormResponses, aggregateFromVotes, aggregateRowForField, aggregateRowsForFields, applyConsentStatements, buildDefaultActionDefinitions, buildDefaultButtonFields, buildDefaultFieldDefinitions, buildDefaultOutcomeFields, buildDefaultSettingsFields, buildNextLabelField, buildPollCloseTask, buildPrevLabelField, buildRecallResolver, buildResolvedAtField, buildSubmitLabelField, buildWinningValuesField, calcExpressionOf, calcUsesSources, calcWeightKey, captureConsent, captureFileRef, computeCalcFields, consentSourcesField, countryField, createKvRateLimiter, createSubmission, defaultActionDefinitions, defaultBodyConverters, defaultFieldDefinitions, defaultFieldDefinitionsByType, defaultIdentify, defaultPresentationDescriptors, defaultValidationRules, defaultValidationRulesByType, defineAction, defineCalcFunction, defineCalcSource, defineCaptchaProvider, defineFormField, definePollOptionSource, definePollType, defineValidationRule, departmentsField, enqueuePollClose, escapeHtml, evaluateCalc, evaluateCondition, fieldHasOptions, fieldKey, fieldTargetParam, fileMimeTypeOptions, formBuilder, formatBytes, formatCalc, formatCalcValue, hasVotedCookie, hcaptchaProvider, interpolate, isPollClosed, localizedIf, manualStrategy, mostVotedStrategy, normalizeCalc, optionLabelsFor, pollTypesOf, recaptchaProvider, recountPollVotes, registerPollCloseTask, renderAllValues, renderAllValuesTable, resolveActions, resolveCalcContext, resolveConsentEntries, resolveConsentSourcesRequest, resolveConsentStatements, resolveDepartmentOptions, resolveEffectivePollOptions, resolveFileRef, resolveFormResultsRequest, resolvePollOptionSources, resolvePollOptions, resolvePollOutcome, resolvePollTypes, resolvePublishedVersionRef, resolveSpamConfig, resolveVotedSubmission, runPollClose, sanitizeUrl, serializeBody, shouldAutoResolvePoll, signFormContext, signPayload, sourceStrategy, stashPollTypes, stateField, textOfBody, toFormDocument, topBucketValues, turnstileProvider, valuesFromSearchParams, verifyFormContext, votedCookieName };
103
+ export { type ActionDefinition, ActionError, type ActionOption, type ActionRegistry, type ActionResult, type ActionRunArgs, type ActionValidateArgs, type ActionsConfig, type AggregateFieldResponsesArgs, type AggregateFormResponsesArgs, type AggregationBucket, type AggregationRow, type AnyActionDefinition, type AnyFormFieldDefinition, type AnyPollOptionSource, type AnyValidationRuleDefinition, type BodyContext, type BodyConverter, type BodyConverterArgs, type BodyRender, type ButtonFieldsOverride, type ButtonsOption, CAPTCHA_TOKEN_KEY, type CalcAllowed, type CalcDisplayConfig, type CalcExpression, type CalcFunction, type CalcResolved, type CalcSource, type CalcSourceResolveArgs, type CalcWeightResolveArgs, type CaptchaProvider, type CaptchaVerifyArgs, type ConsentProof, type ConsentSnapshotMode, type ConsentSourceEntry, type ConsentSourceOption, type ConsentSourcePage, type ConsentSourcesFieldOptions, type ConsentSourcesResolver, type ConsentStatement, type ConsentStatements, type CreateSubmissionArgs, type CreatedSubmission, DEFAULT_COLLECTION_SLUGS, DEFAULT_HONEYPOT_FIELD, DEFAULT_PRESENTATION_NAME, DEFAULT_VOTED_COOKIE_PREFIX, type DefaultButtonFields, type DefaultOutcomeFields, type DefaultSettingsFields, type DepartmentEmailsResolver, type DepartmentOption, type DepartmentsFieldOptions, type EmailActionType, type EmailRender, type EmailRenderArgs, type FieldAggregation, type FieldCondition, type FieldMeta, type FieldTargetParamOptions, type FieldTypeOption, type FieldTypeRegistry, type FieldTypesConfig, type FileFieldConfig, type FileRef, type FileRefError, type FormBuilderCollectionSlugs, type FormBuilderPluginOptions, type FormButtonSettings, type FormContextReference, type FormDocument, type FormFallbackLocale, type FormFallbackLocaleArgs, type FormFallbackLocaleResult, type FormFieldDefinition, type FormFieldFormat, type FormFieldValidate, type FormFieldValueKind, type FormPollSettings, type FormResponseSettings, type FormResultsAccess, type FormResultsAccessArgs, type FromAddressOption, type FromAddressSource, type FromAddressSourceRegistry, type FromAddressesResolver, type HcaptchaProviderOptions, INLINE_DISPATCH_DEADLINE_MS, type IdentifyFn, type OmittableSharedField, type OutcomeFieldsOverride, POLL_CLOSE_TASK_SLUG, POLL_VOTES_SLUG, type FormBuilderPluginOptions as PluginOptions, type PollCloseTaskInput, type PollOption, type PollOptionResolveArgs, type PollOptionSource, type PollOptionSourceOption, type PollOptionSourceRegistry, type PollOptionSourcesConfig, type PollOutcomeStrategy, type PollOutcomeStrategyArgs, type PollTypeRegistry, type PollTypesConfig, type PrefillOptions, type PresentationDensity, type PresentationDescriptor, type PresentationSurface, RESPONDENTS_VALUE, type RateLimitCheckArgs, type RateLimitConfig, type RateLimitResult, type RateLimiter, type RecallResolver, type RecaptchaProviderOptions, type RecipientResolveArgs, type RecipientSource, type RecipientSourceRegistry, type RedirectFieldsOverride, type RedirectOption, type ResolveCalcContextArgs, type ResolveConsentEntriesArgs, type ResolveConsentSourcesRequestArgs, type ResolveConsentSourcesRequestResult, type ResolveConsentStatementsArgs, type ResolveDepartmentOptionsArgs, type ResolveDepartmentsRequestArgs, type ResolveDepartmentsRequestResult, type ResolveEffectivePollOptionsArgs, type ResolveFieldOptionsArgs, type ResolvePollOptionsArgs, type ResolvePollOutcomeArgs, type ResolvePublishedVersionRefArgs, type ResolveResultsRequestArgs, type ResolveResultsRequestResult, type ResponseOption, type RichTextBodyOption, SIGNATURE_HEADER, type SerializeBodyArgs, type SettingsFieldsOverride, type SettingsOption, type SpamConfig, type SpamMetadataConfig, type SpamOption, type SubmissionContextArgs, type SubmissionForm, type SubmissionStatusFilter, type ToFormDocumentOptions, type TurnstileProviderOptions, type UploadsOption, VOTE_SHARDS, type ValidationRuleDefinition, type ValidationRuleOption, type ValidationRuleRegistry, type ValidationRuleResult, type ValidationRulesConfig, type VotedSubmission, aggregateFieldResponses, aggregateFormResponses, aggregateFromVotes, aggregateRowForField, aggregateRowsForFields, applyConsentStatements, buildDefaultActionDefinitions, buildDefaultButtonFields, buildDefaultFieldDefinitions, buildDefaultOutcomeFields, buildDefaultSettingsFields, buildNextLabelField, buildPollCloseTask, buildPrevLabelField, buildRecallResolver, buildResolvedAtField, buildSubmitLabelField, buildWinningValuesField, calcExpressionOf, calcUsesSources, calcWeightKey, captureConsent, captureFileRef, collectionSlugsOf, computeCalcFields, consentSourcesField, countryField, createKvRateLimiter, createSubmission, defaultActionDefinitions, defaultBodyConverters, defaultFieldDefinitions, defaultFieldDefinitionsByType, defaultIdentify, defaultPresentationDescriptors, defaultValidationRules, defaultValidationRulesByType, defineAction, defineCalcFunction, defineCalcSource, defineCaptchaProvider, defineFormField, definePollOptionSource, definePollType, defineValidationRule, departmentsField, enqueuePollClose, escapeHtml, evaluateCalc, evaluateCondition, fieldHasOptions, fieldKey, fieldTargetParam, fileMimeTypeOptions, formBuilder, formatBytes, formatCalc, formatCalcValue, hasVotedCookie, hcaptchaProvider, interpolate, isPollClosed, localizedIf, manualStrategy, mostVotedStrategy, normalizeCalc, optionLabelsFor, pollTypesOf, recaptchaProvider, recountPollVotes, registerPollCloseTask, renderAllValues, renderAllValuesTable, resolveActions, resolveCalcContext, resolveConsentEntries, resolveConsentSourcesRequest, resolveConsentStatements, resolveDepartmentOptions, resolveEffectivePollOptions, resolveFileRef, resolveFormResultsRequest, resolvePollOptionSources, resolvePollOptions, resolvePollOutcome, resolvePollTypes, resolvePublishedVersionRef, resolveSpamConfig, resolveVotedSubmission, runPollClose, sanitizeUrl, serializeBody, shouldAutoResolvePoll, signFormContext, signPayload, sourceStrategy, stashPollTypes, stateField, textOfBody, toFormDocument, topBucketValues, turnstileProvider, valuesFromSearchParams, verifyFormContext, votedCookieName };
103
104
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -18,24 +18,26 @@ import { resolveFileRef } from "./uploads/resolveFileRef.js";
18
18
  import { fileMimeTypeOptions } from "./fields/builtin/file.js";
19
19
  import { buildDefaultFieldDefinitions, defaultFieldDefinitions, defaultFieldDefinitionsByType } from "./fields/builtin/index.js";
20
20
  import { resolveFieldTypes, stashFieldTypes } from "./fields/registry.js";
21
- import { fieldKey } from "./fields/fieldKey.js";
22
- import { calcWeightKey, evaluateCalc } from "./calc/evaluate.js";
23
- import { calcExpressionOf, computeCalcFields } from "./calc/computeCalcFields.js";
24
- import { calcUsesSources, resolveCalcContext } from "./calc/resolveCalcContext.js";
25
- import { textOfBody } from "./actions/body/textOfBody.js";
26
- import { resolveConsentStatements } from "./consent/resolveConsentStatements.js";
27
- import { isPollClosed } from "./form/pollState.js";
21
+ import { DEFAULT_COLLECTION_SLUGS, DEFAULT_VOTED_COOKIE_PREFIX, collectionSlugsOf, resolveCollectionSlugs, resolveVotedCookiePrefix, stashCollectionSlugs } from "./plugin/collectionSlugs.js";
22
+ import { stashFallbackLocale } from "./form/findFormAtLocale.js";
28
23
  import { INLINE_DISPATCH_DEADLINE_MS } from "./actions/dispatch.js";
24
+ import { isPollClosed } from "./form/pollState.js";
29
25
  import { POLL_VOTES_SLUG, RESPONDENTS_VALUE, VOTE_SHARDS } from "./poll/votes/votesCollection.js";
30
26
  import { signFormContext, verifyFormContext } from "./context/formContext.js";
31
27
  import { hasVotedCookie, votedCookieName } from "./submissions/votedCookie.js";
32
28
  import { CAPTCHA_TOKEN_KEY, DEFAULT_HONEYPOT_FIELD } from "./spam/constants.js";
29
+ import { fieldKey } from "./fields/fieldKey.js";
30
+ import { calcWeightKey, evaluateCalc } from "./calc/evaluate.js";
31
+ import { calcExpressionOf, computeCalcFields } from "./calc/computeCalcFields.js";
32
+ import { calcUsesSources, resolveCalcContext } from "./calc/resolveCalcContext.js";
33
33
  import { resolvePollOptions, stashPollOptionSources } from "./poll/resolvePollOptions.js";
34
34
  import { resolveEffectivePollOptions } from "./poll/effectivePollOptions.js";
35
35
  import { evaluateCondition } from "./conditions/evaluate.js";
36
+ import { textOfBody } from "./actions/body/textOfBody.js";
36
37
  import { resolvePublishedVersionRef } from "./consent/resolvePublishedVersionRef.js";
37
38
  import { captureConsent } from "./consent/captureConsent.js";
38
39
  import { captureFileRef } from "./uploads/captureFileRef.js";
40
+ import { resolveConsentStatements } from "./consent/resolveConsentStatements.js";
39
41
  import { aggregateRowForField, aggregateRowsForFields } from "./aggregation/aggregateRows.js";
40
42
  import { aggregateFieldResponses, aggregateFormResponses, fieldHasOptions } from "./aggregation/aggregateResponses.js";
41
43
  import { definePollType } from "./poll/definePollType.js";
@@ -50,7 +52,6 @@ import { resolveFormResultsRequest } from "./aggregation/resolveResultsRequest.j
50
52
  import { resolveConsentSourcesRequest } from "./consent/resolveConsentSourcesRequest.js";
51
53
  import { resolveDepartmentOptions } from "./email/departments.js";
52
54
  import { buildDefaultSettingsFields } from "./collections/settingsFields.js";
53
- import { stashFallbackLocale } from "./form/findFormAtLocale.js";
54
55
  import { readUploadCollectionMimeTypes } from "./plugin/uploadsCollection.js";
55
56
  import { registerCollections } from "./plugin/registerCollections.js";
56
57
  import { registerTranslations } from "./plugin/registerTranslations.js";
@@ -90,6 +91,8 @@ const formBuilder = definePlugin({
90
91
  if (options.disabled === true) return config;
91
92
  const localizeContent = options.localizeContent !== false;
92
93
  const uploads = options.uploads ?? false;
94
+ const slugs = resolveCollectionSlugs(options);
95
+ const votedCookiePrefix = resolveVotedCookiePrefix(options.poll?.cookiePrefix);
93
96
  const calcSources = options.calc?.sources ?? {};
94
97
  const calcFunctions = options.calc?.functions ?? {};
95
98
  assertNoCalcFunctionCollision(calcFunctions);
@@ -129,6 +132,7 @@ const formBuilder = definePlugin({
129
132
  config.custom = stashPollOptionSources(config.custom, pollSourceRegistry);
130
133
  config.custom = stashPollTypes(config.custom, pollTypeRegistry);
131
134
  config.custom = stashFieldTypes(config.custom, registry);
135
+ config.custom = stashCollectionSlugs(config.custom, slugs, votedCookiePrefix);
132
136
  if (consentSources) config.custom = stashConsentSources(config.custom, consentSources);
133
137
  if (options.fallbackLocale) config.custom = stashFallbackLocale(config.custom, options.fallbackLocale);
134
138
  registerTranslations(config, options.translations);
@@ -164,12 +168,13 @@ const formBuilder = definePlugin({
164
168
  fromSources,
165
169
  departments,
166
170
  redirectRelationships: options.redirectRelationships,
171
+ slugs,
167
172
  overrides: options.overrides
168
173
  });
169
174
  return config;
170
175
  }
171
176
  });
172
177
  //#endregion
173
- export { ActionError, CAPTCHA_TOKEN_KEY, DEFAULT_HONEYPOT_FIELD, DEFAULT_PRESENTATION_NAME, INLINE_DISPATCH_DEADLINE_MS, POLL_CLOSE_TASK_SLUG, POLL_VOTES_SLUG, RESPONDENTS_VALUE, SIGNATURE_HEADER, VOTE_SHARDS, aggregateFieldResponses, aggregateFormResponses, aggregateFromVotes, aggregateRowForField, aggregateRowsForFields, applyConsentStatements, buildDefaultActionDefinitions, buildDefaultButtonFields, buildDefaultFieldDefinitions, buildDefaultOutcomeFields, buildDefaultSettingsFields, buildNextLabelField, buildPollCloseTask, buildPrevLabelField, buildRecallResolver, buildResolvedAtField, buildSubmitLabelField, buildWinningValuesField, calcExpressionOf, calcUsesSources, calcWeightKey, captureConsent, captureFileRef, computeCalcFields, consentSourcesField, countryField, createKvRateLimiter, createSubmission, defaultActionDefinitions, defaultBodyConverters, defaultFieldDefinitions, defaultFieldDefinitionsByType, defaultIdentify, defaultPresentationDescriptors, defaultValidationRules, defaultValidationRulesByType, defineAction, defineCalcFunction, defineCalcSource, defineCaptchaProvider, defineFormField, definePollOptionSource, definePollType, defineValidationRule, departmentsField, enqueuePollClose, escapeHtml, evaluateCalc, evaluateCondition, fieldHasOptions, fieldKey, fieldTargetParam, fileMimeTypeOptions, formBuilder, formatBytes, formatCalc, formatCalcValue, hasVotedCookie, hcaptchaProvider, interpolate, isPollClosed, localizedIf, manualStrategy, mostVotedStrategy, normalizeCalc, optionLabelsFor, pollTypesOf, recaptchaProvider, recountPollVotes, registerPollCloseTask, renderAllValues, renderAllValuesTable, resolveActions, resolveCalcContext, resolveConsentEntries, resolveConsentSourcesRequest, resolveConsentStatements, resolveDepartmentOptions, resolveEffectivePollOptions, resolveFileRef, resolveFormResultsRequest, resolvePollOptionSources, resolvePollOptions, resolvePollOutcome, resolvePollTypes, resolvePublishedVersionRef, resolveSpamConfig, resolveVotedSubmission, runPollClose, sanitizeUrl, serializeBody, shouldAutoResolvePoll, signFormContext, signPayload, sourceStrategy, stashPollTypes, stateField, textOfBody, toFormDocument, topBucketValues, turnstileProvider, valuesFromSearchParams, verifyFormContext, votedCookieName };
178
+ export { ActionError, CAPTCHA_TOKEN_KEY, DEFAULT_COLLECTION_SLUGS, DEFAULT_HONEYPOT_FIELD, DEFAULT_PRESENTATION_NAME, DEFAULT_VOTED_COOKIE_PREFIX, INLINE_DISPATCH_DEADLINE_MS, POLL_CLOSE_TASK_SLUG, POLL_VOTES_SLUG, RESPONDENTS_VALUE, SIGNATURE_HEADER, VOTE_SHARDS, aggregateFieldResponses, aggregateFormResponses, aggregateFromVotes, aggregateRowForField, aggregateRowsForFields, applyConsentStatements, buildDefaultActionDefinitions, buildDefaultButtonFields, buildDefaultFieldDefinitions, buildDefaultOutcomeFields, buildDefaultSettingsFields, buildNextLabelField, buildPollCloseTask, buildPrevLabelField, buildRecallResolver, buildResolvedAtField, buildSubmitLabelField, buildWinningValuesField, calcExpressionOf, calcUsesSources, calcWeightKey, captureConsent, captureFileRef, collectionSlugsOf, computeCalcFields, consentSourcesField, countryField, createKvRateLimiter, createSubmission, defaultActionDefinitions, defaultBodyConverters, defaultFieldDefinitions, defaultFieldDefinitionsByType, defaultIdentify, defaultPresentationDescriptors, defaultValidationRules, defaultValidationRulesByType, defineAction, defineCalcFunction, defineCalcSource, defineCaptchaProvider, defineFormField, definePollOptionSource, definePollType, defineValidationRule, departmentsField, enqueuePollClose, escapeHtml, evaluateCalc, evaluateCondition, fieldHasOptions, fieldKey, fieldTargetParam, fileMimeTypeOptions, formBuilder, formatBytes, formatCalc, formatCalcValue, hasVotedCookie, hcaptchaProvider, interpolate, isPollClosed, localizedIf, manualStrategy, mostVotedStrategy, normalizeCalc, optionLabelsFor, pollTypesOf, recaptchaProvider, recountPollVotes, registerPollCloseTask, renderAllValues, renderAllValuesTable, resolveActions, resolveCalcContext, resolveConsentEntries, resolveConsentSourcesRequest, resolveConsentStatements, resolveDepartmentOptions, resolveEffectivePollOptions, resolveFileRef, resolveFormResultsRequest, resolvePollOptionSources, resolvePollOptions, resolvePollOutcome, resolvePollTypes, resolvePublishedVersionRef, resolveSpamConfig, resolveVotedSubmission, runPollClose, sanitizeUrl, serializeBody, shouldAutoResolvePoll, signFormContext, signPayload, sourceStrategy, stashPollTypes, stateField, textOfBody, toFormDocument, topBucketValues, turnstileProvider, valuesFromSearchParams, verifyFormContext, votedCookieName };
174
179
 
175
180
  //# sourceMappingURL=index.js.map