@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
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { type Config, definePlugin } from 'payload'\nimport { assertNoActionBlockCollision } from './actions/assertNoBlockCollision'\nimport { buildDefaultActionDefinitions } from './actions/builtin'\nimport { resolveActions } from './actions/registry'\nimport { assertNoCalcFunctionCollision, assertValidCalcSourceKeys } from './calc/registry'\nimport { stashConsentSources } from './consent/resolveConsentEntries'\nimport { buildDefaultFieldDefinitions } from './fields/builtin'\nimport { resolveFieldTypes, stashFieldTypes } from './fields/registry'\nimport { stashFallbackLocale } from './form/findFormAtLocale'\nimport type { FormBuilderPluginOptions } from './options'\nimport { registerCollections } from './plugin/registerCollections'\nimport { registerTranslations } from './plugin/registerTranslations'\nimport { readUploadCollectionMimeTypes } from './plugin/uploadsCollection'\nimport { resolvePollTypes, stashPollTypes } from './poll/pollTypeRegistry'\nimport { resolvePollOptionSources } from './poll/registry'\nimport { stashPollOptionSources } from './poll/resolvePollOptions'\nimport { resolveSpamConfig } from './spam/resolveSpam'\nimport { defaultValidationRules } from './validation/builtin'\nimport { resolveValidationRules } from './validation/registry'\n\nexport const formBuilder = definePlugin<FormBuilderPluginOptions>({\n\tslug: '@10x-media/form-builder',\n\torder: 50,\n\tplugin: ({ config, plugins, ...options }): Config => {\n\t\tif (options.disabled === true) {\n\t\t\treturn config\n\t\t}\n\t\tconst localizeContent = options.localizeContent !== false\n\t\tconst uploads = options.uploads ?? false\n\t\tconst calcSources = options.calc?.sources ?? {}\n\t\tconst calcFunctions = options.calc?.functions ?? {}\n\t\t// Fail fast: the evaluator resolves built-ins first, so a colliding custom function could never\n\t\t// run; a source key with a space would make the weight-map key (`calcWeightKey`) ambiguous.\n\t\tassertNoCalcFunctionCollision(calcFunctions)\n\t\tassertValidCalcSourceKeys(calcSources)\n\t\t// The allowed extension names, threaded into every normalizeCalc gate (the calculation field's\n\t\t// validate and the forms beforeValidate) so a stored expression can only ever reference a\n\t\t// registered source or function.\n\t\tconst calcAllowed = {\n\t\t\tsources: new Set(Object.keys(calcSources)),\n\t\t\tfunctions: new Set(Object.keys(calcFunctions)),\n\t\t}\n\t\t// Serializable source metadata for the builder UI: key, label, and implemented modes. The\n\t\t// resolver functions themselves never leave the server.\n\t\tconst calcSourceMeta = Object.entries(calcSources).map(([key, source]) => ({\n\t\t\tkey,\n\t\t\tlabel: source.label,\n\t\t\tscalar: typeof source.resolve === 'function',\n\t\t\tweights: typeof source.resolveWeights === 'function',\n\t\t}))\n\t\t// The file field's MIME picker is constrained to what the host upload collection accepts: the\n\t\t// explicit `uploads.mimeTypes` override, else the collection's own `upload.mimeTypes`. Read here,\n\t\t// before the field registry freezes below (attachUploadsCollection runs too late).\n\t\tconst uploadMimeTypes =\n\t\t\tuploads === false\n\t\t\t\t? undefined\n\t\t\t\t: (uploads.mimeTypes ?? readUploadCollectionMimeTypes(config, uploads.collection))\n\t\tconst consentSources = options.consent?.sources\n\t\t// A built-in with nowhere to point never enters the registry, so an author is never offered a\n\t\t// field that cannot work: file without an uploads collection has nowhere to store anything,\n\t\t// consent without sources has no statement to reference. An explicit `fields.file` /\n\t\t// `fields.consent` definition remains a developer choice.\n\t\tconst defaultFieldDefinitions = buildDefaultFieldDefinitions(\n\t\t\tlocalizeContent,\n\t\t\toptions.richText?.editor,\n\t\t\tuploadMimeTypes,\n\t\t\tcalcAllowed,\n\t\t\tcalcSourceMeta\n\t\t).filter(\n\t\t\t(definition) =>\n\t\t\t\t(uploads !== false || definition.type !== 'file') &&\n\t\t\t\t(consentSources !== undefined || definition.type !== 'consent')\n\t\t)\n\t\tconst registry = resolveFieldTypes(defaultFieldDefinitions, options.fields)\n\t\tconst ruleRegistry = resolveValidationRules(defaultValidationRules, options.rules)\n\t\tconst fromAddresses = options.email?.fromAddresses\n\t\tconst fromSources = options.email?.fromSources\n\t\tconst departments = options.email?.departments\n\t\tconst actionRegistry = resolveActions(\n\t\t\tbuildDefaultActionDefinitions({\n\t\t\t\tlocalize: localizeContent,\n\t\t\t\teditor: options.richText?.bodyEditor ?? options.richText?.editor,\n\t\t\t\tfromAddresses,\n\t\t\t\tfromSources,\n\t\t\t\tdepartments,\n\t\t\t\tlocalizeRecipients: options.email?.localizeRecipients,\n\t\t\t\trecipients: options.email?.recipients,\n\t\t\t\trecipientSources: options.email?.recipientSources,\n\t\t\t\trender: options.email?.render,\n\t\t\t}),\n\t\t\toptions.actions\n\t\t)\n\t\t// Fail fast if a custom action type collides with a host block slug (Payload resolves blocks\n\t\t// globally by slug, which would silently merge the content block's fields into saved actions).\n\t\tassertNoActionBlockCollision(config, actionRegistry)\n\t\tconst spam = resolveSpamConfig(options.spam)\n\t\tconst pollSourceRegistry = resolvePollOptionSources(options.poll?.sources)\n\t\tconst pollTypeRegistry = resolvePollTypes(options.poll?.types)\n\t\t// Stashed on config.custom so the root-level resolvePollOptions, resolveEffectivePollOptions,\n\t\t// resolvePollOutcome, and resolveConsentStatements helpers can reach these through\n\t\t// `payload.config` at request time, without threading plugin state through the host's own\n\t\t// server code. The field-type registry rides along so resolveEffectivePollOptions can look up a\n\t\t// results field's definition (and its `resolveOptions`) from a bare `payload` instance.\n\t\tconfig.custom = stashPollOptionSources(config.custom, pollSourceRegistry)\n\t\tconfig.custom = stashPollTypes(config.custom, pollTypeRegistry)\n\t\tconfig.custom = stashFieldTypes(config.custom, registry)\n\t\tif (consentSources) {\n\t\t\tconfig.custom = stashConsentSources(config.custom, consentSources)\n\t\t}\n\t\tif (options.fallbackLocale) {\n\t\t\tconfig.custom = stashFallbackLocale(config.custom, options.fallbackLocale)\n\t\t}\n\t\tregisterTranslations(config, options.translations)\n\t\tregisterCollections({\n\t\t\tconfig,\n\t\t\tregistry,\n\t\t\truleRegistry,\n\t\t\tcalcAllowed,\n\t\t\tcalcSources,\n\t\t\tcalcFunctions,\n\t\t\tconsentSources,\n\t\t\tconsentSnapshot: options.consent?.snapshot ?? 'both',\n\t\t\tconsentResolveOnRead: options.consent?.resolveOnRead,\n\t\t\tactionRegistry,\n\t\t\trichText: options.richText,\n\t\t\thasJobsPlugin: Boolean(plugins['@10x-media/jobs']),\n\t\t\tdispatchDeadlineMs: options.dispatch?.deadlineMs,\n\t\t\tevents: options.events,\n\t\t\tuploads,\n\t\t\tspam,\n\t\t\tshowSubmissionRawFields: options.showSubmissionRawFields ?? false,\n\t\t\tlocalizeContent,\n\t\t\tresultsAccess: options.results?.access,\n\t\t\tvotedCookie: options.poll?.votedCookie === true,\n\t\t\tpollSourceRegistry,\n\t\t\tpollTypeRegistry,\n\t\t\toutcomeFields: options.poll?.outcomeFields,\n\t\t\tpollVotes: options.poll?.votes === false ? false : (options.poll?.votes ?? {}),\n\t\t\tbuttons: options.buttons,\n\t\t\tsettings: options.settings,\n\t\t\tresponse: options.response,\n\t\t\tfromAddresses,\n\t\t\tfromSources,\n\t\t\tdepartments,\n\t\t\tredirectRelationships: options.redirectRelationships,\n\t\t\toverrides: options.overrides,\n\t\t})\n\t\treturn config\n\t},\n})\n\nexport type {\n\tBodyConverter,\n\tBodyConverterArgs,\n\tBodyRender,\n} from './actions/body/converters'\nexport { defaultBodyConverters, sanitizeUrl } from './actions/body/converters'\nexport { escapeHtml } from './actions/body/escapeHtml'\nexport type {\n\tBodyContext,\n\tRichTextBodyOption,\n\tSerializeBodyArgs,\n} from './actions/body/serializeBody'\nexport { serializeBody } from './actions/body/serializeBody'\nexport { textOfBody } from './actions/body/textOfBody'\nexport { renderAllValues, renderAllValuesTable } from './actions/body/wildcards'\nexport { buildDefaultActionDefinitions, defaultActionDefinitions } from './actions/builtin'\nexport type {\n\tActionDefinition,\n\tActionRunArgs,\n\tActionValidateArgs,\n\tAnyActionDefinition,\n} from './actions/defineAction'\nexport { ActionError, defineAction } from './actions/defineAction'\nexport { INLINE_DISPATCH_DEADLINE_MS } from './actions/dispatch'\nexport type { EmailActionType, EmailRender, EmailRenderArgs } from './actions/emailRender'\nexport type {\n\tFromAddressesResolver,\n\tFromAddressOption,\n\tFromAddressSource,\n\tFromAddressSourceRegistry,\n} from './actions/fromAddresses'\nexport type {\n\tRecipientResolveArgs,\n\tRecipientSource,\n\tRecipientSourceRegistry,\n} from './actions/recipientSources'\nexport type { ActionOption, ActionRegistry, ActionsConfig } from './actions/registry'\nexport { resolveActions } from './actions/registry'\nexport type { ActionResult } from './actions/runActions'\nexport { SIGNATURE_HEADER, signPayload } from './actions/sign'\nexport type { SubmissionContextArgs, SubmissionForm } from './actions/submissionContext'\nexport type {\n\tAggregateFieldResponsesArgs,\n\tAggregateFormResponsesArgs,\n} from './aggregation/aggregateResponses'\nexport {\n\taggregateFieldResponses,\n\taggregateFormResponses,\n\tfieldHasOptions,\n} from './aggregation/aggregateResponses'\nexport { aggregateRowForField, aggregateRowsForFields } from './aggregation/aggregateRows'\nexport type {\n\tFormResultsAccess,\n\tFormResultsAccessArgs,\n\tResolveResultsRequestArgs,\n\tResolveResultsRequestResult,\n} from './aggregation/resolveResultsRequest'\nexport { resolveFormResultsRequest } from './aggregation/resolveResultsRequest'\nexport type {\n\tAggregationBucket,\n\tAggregationRow,\n\tFieldAggregation,\n\tFieldMeta,\n\tSubmissionStatusFilter,\n} from './aggregation/types'\nexport { calcExpressionOf, computeCalcFields } from './calc/computeCalcFields'\nexport type { CalcResolved } from './calc/evaluate'\nexport { calcWeightKey, evaluateCalc } from './calc/evaluate'\nexport { formatCalc } from './calc/formatCalc'\nexport type { CalcDisplayConfig } from './calc/formatCalcValue'\nexport { formatCalcValue } from './calc/formatCalcValue'\nexport type { CalcAllowed } from './calc/normalizeCalc'\nexport { normalizeCalc } from './calc/normalizeCalc'\nexport type {\n\tCalcFunction,\n\tCalcSource,\n\tCalcSourceResolveArgs,\n\tCalcWeightResolveArgs,\n} from './calc/registry'\nexport { defineCalcFunction, defineCalcSource } from './calc/registry'\nexport type { ResolveCalcContextArgs } from './calc/resolveCalcContext'\nexport { calcUsesSources, resolveCalcContext } from './calc/resolveCalcContext'\nexport type { CalcExpression } from './calc/types'\nexport type {\n\tButtonFieldsOverride,\n\tButtonsOption,\n\tDefaultButtonFields,\n} from './collections/buttonFields'\nexport {\n\tbuildDefaultButtonFields,\n\tbuildNextLabelField,\n\tbuildPrevLabelField,\n\tbuildSubmitLabelField,\n} from './collections/buttonFields'\nexport type {\n\tRedirectFieldsOverride,\n\tRedirectOption,\n\tResponseOption,\n} from './collections/redirectFields'\nexport type {\n\tDefaultSettingsFields,\n\tSettingsFieldsOverride,\n\tSettingsOption,\n} from './collections/settingsFields'\nexport { buildDefaultSettingsFields } from './collections/settingsFields'\nexport { evaluateCondition } from './conditions/evaluate'\nexport type { FieldCondition } from './conditions/types'\nexport { applyConsentStatements } from './consent/applyConsentStatements'\nexport type { ConsentProof, ConsentSnapshotMode } from './consent/captureConsent'\nexport { captureConsent } from './consent/captureConsent'\nexport type { ConsentSourcesFieldOptions } from './consent/consentSourcesField'\nexport { consentSourcesField } from './consent/consentSourcesField'\nexport type { ResolveConsentEntriesArgs } from './consent/resolveConsentEntries'\nexport { resolveConsentEntries } from './consent/resolveConsentEntries'\nexport type {\n\tConsentSourceOption,\n\tResolveConsentSourcesRequestArgs,\n\tResolveConsentSourcesRequestResult,\n} from './consent/resolveConsentSourcesRequest'\nexport { resolveConsentSourcesRequest } from './consent/resolveConsentSourcesRequest'\nexport type {\n\tConsentStatement,\n\tConsentStatements,\n\tResolveConsentStatementsArgs,\n} from './consent/resolveConsentStatements'\nexport { resolveConsentStatements } from './consent/resolveConsentStatements'\nexport type { ResolvePublishedVersionRefArgs } from './consent/resolvePublishedVersionRef'\nexport { resolvePublishedVersionRef } from './consent/resolvePublishedVersionRef'\nexport type {\n\tConsentSourceEntry,\n\tConsentSourcePage,\n\tConsentSourcesResolver,\n} from './consent/types'\nexport type { FormContextReference } from './context/formContext'\nexport { signFormContext, verifyFormContext } from './context/formContext'\nexport type {\n\tDepartmentEmailsResolver,\n\tDepartmentOption,\n\tResolveDepartmentOptionsArgs,\n\tResolveDepartmentsRequestArgs,\n\tResolveDepartmentsRequestResult,\n} from './email/departments'\nexport { resolveDepartmentOptions } from './email/departments'\nexport type { DepartmentsFieldOptions } from './email/departmentsField'\nexport { departmentsField } from './email/departmentsField'\nexport {\n\tbuildDefaultFieldDefinitions,\n\tdefaultFieldDefinitions,\n\tdefaultFieldDefinitionsByType,\n} from './fields/builtin'\nexport { countryField } from './fields/builtin/country'\nexport { fileMimeTypeOptions } from './fields/builtin/file'\nexport { stateField } from './fields/builtin/state'\nexport { defineFormField } from './fields/defineFormField'\nexport { fieldKey } from './fields/fieldKey'\nexport { localizedIf } from './fields/localizedIf'\nexport type { FieldTypeOption, FieldTypeRegistry, FieldTypesConfig } from './fields/registry'\nexport type {\n\tAnyFormFieldDefinition,\n\tFormFieldDefinition,\n\tFormFieldFormat,\n\tFormFieldValidate,\n\tFormFieldValueKind,\n\tOmittableSharedField,\n\tResolveFieldOptionsArgs,\n} from './fields/types'\nexport type {\n\tFormFallbackLocale,\n\tFormFallbackLocaleArgs,\n\tFormFallbackLocaleResult,\n} from './form/findFormAtLocale'\nexport { isPollClosed } from './form/pollState'\nexport type { ToFormDocumentOptions } from './form/toFormDocument'\nexport { toFormDocument } from './form/toFormDocument'\nexport type {\n\tFormButtonSettings,\n\tFormDocument,\n\tFormPollSettings,\n\tFormResponseSettings,\n} from './form/types'\nexport type {\n\tFormBuilderPluginOptions,\n\tFormBuilderPluginOptions as PluginOptions,\n} from './options'\nexport type { UploadsOption } from './plugin/uploadsCollection'\nexport type { PollCloseTaskInput } from './poll/closeJob'\nexport {\n\tbuildPollCloseTask,\n\tenqueuePollClose,\n\tPOLL_CLOSE_TASK_SLUG,\n\tregisterPollCloseTask,\n\trunPollClose,\n\tshouldAutoResolvePoll,\n} from './poll/closeJob'\nexport type {\n\tAnyPollOptionSource,\n\tPollOption,\n\tPollOptionResolveArgs,\n\tPollOptionSource,\n} from './poll/definePollOptionSource'\nexport { definePollOptionSource } from './poll/definePollOptionSource'\nexport type { PollOutcomeStrategy, PollOutcomeStrategyArgs } from './poll/definePollType'\nexport { definePollType } from './poll/definePollType'\nexport type { ResolveEffectivePollOptionsArgs } from './poll/effectivePollOptions'\nexport { resolveEffectivePollOptions } from './poll/effectivePollOptions'\nexport { mostVotedStrategy, topBucketValues } from './poll/mostVoted'\nexport type { DefaultOutcomeFields, OutcomeFieldsOverride } from './poll/outcomeFields'\nexport {\n\tbuildDefaultOutcomeFields,\n\tbuildResolvedAtField,\n\tbuildWinningValuesField,\n} from './poll/outcomeFields'\nexport type { PollTypeRegistry, PollTypesConfig } from './poll/pollTypeRegistry'\nexport {\n\tmanualStrategy,\n\tpollTypesOf,\n\tresolvePollTypes,\n\tsourceStrategy,\n\tstashPollTypes,\n} from './poll/pollTypeRegistry'\nexport type {\n\tPollOptionSourceOption,\n\tPollOptionSourceRegistry,\n\tPollOptionSourcesConfig,\n} from './poll/registry'\nexport { resolvePollOptionSources } from './poll/registry'\nexport type { ResolvePollOptionsArgs } from './poll/resolvePollOptions'\nexport { resolvePollOptions } from './poll/resolvePollOptions'\nexport type { ResolvePollOutcomeArgs } from './poll/resolvePollOutcome'\nexport { resolvePollOutcome } from './poll/resolvePollOutcome'\nexport { aggregateFromVotes } from './poll/votes/aggregateFromVotes'\nexport { recountPollVotes } from './poll/votes/recountPollVotes'\nexport { POLL_VOTES_SLUG, RESPONDENTS_VALUE, VOTE_SHARDS } from './poll/votes/votesCollection'\nexport type { PrefillOptions } from './prefill/valuesFromSearchParams'\nexport { valuesFromSearchParams } from './prefill/valuesFromSearchParams'\nexport { DEFAULT_PRESENTATION_NAME, defaultPresentationDescriptors } from './presentations/defaults'\nexport type {\n\tPresentationDensity,\n\tPresentationDescriptor,\n\tPresentationSurface,\n} from './presentations/types'\nexport { interpolate } from './recall/interpolate'\nexport type { RecallResolver } from './recall/resolver'\nexport { buildRecallResolver, optionLabelsFor } from './recall/resolver'\nexport { defineCaptchaProvider } from './spam/captcha'\nexport { CAPTCHA_TOKEN_KEY, DEFAULT_HONEYPOT_FIELD } from './spam/constants'\nexport { defaultIdentify } from './spam/identify'\nexport type { HcaptchaProviderOptions } from './spam/providers/hcaptcha'\nexport { hcaptchaProvider } from './spam/providers/hcaptcha'\nexport type { RecaptchaProviderOptions } from './spam/providers/recaptcha'\nexport { recaptchaProvider } from './spam/providers/recaptcha'\nexport type { TurnstileProviderOptions } from './spam/providers/turnstile'\nexport { turnstileProvider } from './spam/providers/turnstile'\nexport { createKvRateLimiter } from './spam/rateLimiter'\nexport { resolveSpamConfig } from './spam/resolveSpam'\nexport type {\n\tCaptchaProvider,\n\tCaptchaVerifyArgs,\n\tIdentifyFn,\n\tRateLimitCheckArgs,\n\tRateLimitConfig,\n\tRateLimiter,\n\tRateLimitResult,\n\tSpamConfig,\n\tSpamMetadataConfig,\n\tSpamOption,\n} from './spam/types'\nexport type { CreatedSubmission, CreateSubmissionArgs } from './submissions/createSubmission'\nexport { createSubmission } from './submissions/createSubmission'\nexport type { VotedSubmission } from './submissions/resolveVotedSubmission'\nexport { resolveVotedSubmission } from './submissions/resolveVotedSubmission'\nexport { hasVotedCookie, votedCookieName } from './submissions/votedCookie'\nexport { captureFileRef } from './uploads/captureFileRef'\nexport { formatBytes } from './uploads/formatBytes'\nexport { resolveFileRef } from './uploads/resolveFileRef'\nexport type { FileFieldConfig, FileRef, FileRefError } from './uploads/types'\nexport { defaultValidationRules, defaultValidationRulesByType } from './validation/builtin'\nexport { defineValidationRule } from './validation/defineValidationRule'\nexport type { FieldTargetParamOptions } from './validation/fieldTargetParam'\nexport { fieldTargetParam } from './validation/fieldTargetParam'\nexport type {\n\tValidationRuleOption,\n\tValidationRuleRegistry,\n\tValidationRulesConfig,\n} from './validation/registry'\nexport type {\n\tAnyValidationRuleDefinition,\n\tValidationRuleDefinition,\n\tValidationRuleResult,\n} from './validation/types'\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { type Config, definePlugin } from 'payload'\nimport { assertNoActionBlockCollision } from './actions/assertNoBlockCollision'\nimport { buildDefaultActionDefinitions } from './actions/builtin'\nimport { resolveActions } from './actions/registry'\nimport { assertNoCalcFunctionCollision, assertValidCalcSourceKeys } from './calc/registry'\nimport { stashConsentSources } from './consent/resolveConsentEntries'\nimport { buildDefaultFieldDefinitions } from './fields/builtin'\nimport { resolveFieldTypes, stashFieldTypes } from './fields/registry'\nimport { stashFallbackLocale } from './form/findFormAtLocale'\nimport type { FormBuilderPluginOptions } from './options'\nimport {\n\tresolveCollectionSlugs,\n\tresolveVotedCookiePrefix,\n\tstashCollectionSlugs,\n} from './plugin/collectionSlugs'\nimport { registerCollections } from './plugin/registerCollections'\nimport { registerTranslations } from './plugin/registerTranslations'\nimport { readUploadCollectionMimeTypes } from './plugin/uploadsCollection'\nimport { resolvePollTypes, stashPollTypes } from './poll/pollTypeRegistry'\nimport { resolvePollOptionSources } from './poll/registry'\nimport { stashPollOptionSources } from './poll/resolvePollOptions'\nimport { resolveSpamConfig } from './spam/resolveSpam'\nimport { defaultValidationRules } from './validation/builtin'\nimport { resolveValidationRules } from './validation/registry'\n\nexport const formBuilder = definePlugin<FormBuilderPluginOptions>({\n\tslug: '@10x-media/form-builder',\n\torder: 50,\n\tplugin: ({ config, plugins, ...options }): Config => {\n\t\tif (options.disabled === true) {\n\t\t\treturn config\n\t\t}\n\t\tconst localizeContent = options.localizeContent !== false\n\t\tconst uploads = options.uploads ?? false\n\t\t// Resolved once, validated at boot: collection builders receive these directly, and every\n\t\t// runtime path (jobs, endpoints, host-called helpers) reads the same object off the config.\n\t\tconst slugs = resolveCollectionSlugs(options)\n\t\tconst votedCookiePrefix = resolveVotedCookiePrefix(options.poll?.cookiePrefix)\n\t\tconst calcSources = options.calc?.sources ?? {}\n\t\tconst calcFunctions = options.calc?.functions ?? {}\n\t\t// Fail fast: the evaluator resolves built-ins first, so a colliding custom function could never\n\t\t// run; a source key with a space would make the weight-map key (`calcWeightKey`) ambiguous.\n\t\tassertNoCalcFunctionCollision(calcFunctions)\n\t\tassertValidCalcSourceKeys(calcSources)\n\t\t// The allowed extension names, threaded into every normalizeCalc gate (the calculation field's\n\t\t// validate and the forms beforeValidate) so a stored expression can only ever reference a\n\t\t// registered source or function.\n\t\tconst calcAllowed = {\n\t\t\tsources: new Set(Object.keys(calcSources)),\n\t\t\tfunctions: new Set(Object.keys(calcFunctions)),\n\t\t}\n\t\t// Serializable source metadata for the builder UI: key, label, and implemented modes. The\n\t\t// resolver functions themselves never leave the server.\n\t\tconst calcSourceMeta = Object.entries(calcSources).map(([key, source]) => ({\n\t\t\tkey,\n\t\t\tlabel: source.label,\n\t\t\tscalar: typeof source.resolve === 'function',\n\t\t\tweights: typeof source.resolveWeights === 'function',\n\t\t}))\n\t\t// The file field's MIME picker is constrained to what the host upload collection accepts: the\n\t\t// explicit `uploads.mimeTypes` override, else the collection's own `upload.mimeTypes`. Read here,\n\t\t// before the field registry freezes below (attachUploadsCollection runs too late).\n\t\tconst uploadMimeTypes =\n\t\t\tuploads === false\n\t\t\t\t? undefined\n\t\t\t\t: (uploads.mimeTypes ?? readUploadCollectionMimeTypes(config, uploads.collection))\n\t\tconst consentSources = options.consent?.sources\n\t\t// A built-in with nowhere to point never enters the registry, so an author is never offered a\n\t\t// field that cannot work: file without an uploads collection has nowhere to store anything,\n\t\t// consent without sources has no statement to reference. An explicit `fields.file` /\n\t\t// `fields.consent` definition remains a developer choice.\n\t\tconst defaultFieldDefinitions = buildDefaultFieldDefinitions(\n\t\t\tlocalizeContent,\n\t\t\toptions.richText?.editor,\n\t\t\tuploadMimeTypes,\n\t\t\tcalcAllowed,\n\t\t\tcalcSourceMeta\n\t\t).filter(\n\t\t\t(definition) =>\n\t\t\t\t(uploads !== false || definition.type !== 'file') &&\n\t\t\t\t(consentSources !== undefined || definition.type !== 'consent')\n\t\t)\n\t\tconst registry = resolveFieldTypes(defaultFieldDefinitions, options.fields)\n\t\tconst ruleRegistry = resolveValidationRules(defaultValidationRules, options.rules)\n\t\tconst fromAddresses = options.email?.fromAddresses\n\t\tconst fromSources = options.email?.fromSources\n\t\tconst departments = options.email?.departments\n\t\tconst actionRegistry = resolveActions(\n\t\t\tbuildDefaultActionDefinitions({\n\t\t\t\tlocalize: localizeContent,\n\t\t\t\teditor: options.richText?.bodyEditor ?? options.richText?.editor,\n\t\t\t\tfromAddresses,\n\t\t\t\tfromSources,\n\t\t\t\tdepartments,\n\t\t\t\tlocalizeRecipients: options.email?.localizeRecipients,\n\t\t\t\trecipients: options.email?.recipients,\n\t\t\t\trecipientSources: options.email?.recipientSources,\n\t\t\t\trender: options.email?.render,\n\t\t\t}),\n\t\t\toptions.actions\n\t\t)\n\t\t// Fail fast if a custom action type collides with a host block slug (Payload resolves blocks\n\t\t// globally by slug, which would silently merge the content block's fields into saved actions).\n\t\tassertNoActionBlockCollision(config, actionRegistry)\n\t\tconst spam = resolveSpamConfig(options.spam)\n\t\tconst pollSourceRegistry = resolvePollOptionSources(options.poll?.sources)\n\t\tconst pollTypeRegistry = resolvePollTypes(options.poll?.types)\n\t\t// Stashed on config.custom so the root-level resolvePollOptions, resolveEffectivePollOptions,\n\t\t// resolvePollOutcome, and resolveConsentStatements helpers can reach these through\n\t\t// `payload.config` at request time, without threading plugin state through the host's own\n\t\t// server code. The field-type registry rides along so resolveEffectivePollOptions can look up a\n\t\t// results field's definition (and its `resolveOptions`) from a bare `payload` instance.\n\t\tconfig.custom = stashPollOptionSources(config.custom, pollSourceRegistry)\n\t\tconfig.custom = stashPollTypes(config.custom, pollTypeRegistry)\n\t\tconfig.custom = stashFieldTypes(config.custom, registry)\n\t\tconfig.custom = stashCollectionSlugs(config.custom, slugs, votedCookiePrefix)\n\t\tif (consentSources) {\n\t\t\tconfig.custom = stashConsentSources(config.custom, consentSources)\n\t\t}\n\t\tif (options.fallbackLocale) {\n\t\t\tconfig.custom = stashFallbackLocale(config.custom, options.fallbackLocale)\n\t\t}\n\t\tregisterTranslations(config, options.translations)\n\t\tregisterCollections({\n\t\t\tconfig,\n\t\t\tregistry,\n\t\t\truleRegistry,\n\t\t\tcalcAllowed,\n\t\t\tcalcSources,\n\t\t\tcalcFunctions,\n\t\t\tconsentSources,\n\t\t\tconsentSnapshot: options.consent?.snapshot ?? 'both',\n\t\t\tconsentResolveOnRead: options.consent?.resolveOnRead,\n\t\t\tactionRegistry,\n\t\t\trichText: options.richText,\n\t\t\thasJobsPlugin: Boolean(plugins['@10x-media/jobs']),\n\t\t\tdispatchDeadlineMs: options.dispatch?.deadlineMs,\n\t\t\tevents: options.events,\n\t\t\tuploads,\n\t\t\tspam,\n\t\t\tshowSubmissionRawFields: options.showSubmissionRawFields ?? false,\n\t\t\tlocalizeContent,\n\t\t\tresultsAccess: options.results?.access,\n\t\t\tvotedCookie: options.poll?.votedCookie === true,\n\t\t\tpollSourceRegistry,\n\t\t\tpollTypeRegistry,\n\t\t\toutcomeFields: options.poll?.outcomeFields,\n\t\t\tpollVotes: options.poll?.votes === false ? false : (options.poll?.votes ?? {}),\n\t\t\tbuttons: options.buttons,\n\t\t\tsettings: options.settings,\n\t\t\tresponse: options.response,\n\t\t\tfromAddresses,\n\t\t\tfromSources,\n\t\t\tdepartments,\n\t\t\tredirectRelationships: options.redirectRelationships,\n\t\t\tslugs,\n\t\t\toverrides: options.overrides,\n\t\t})\n\t\treturn config\n\t},\n})\n\nexport type {\n\tBodyConverter,\n\tBodyConverterArgs,\n\tBodyRender,\n} from './actions/body/converters'\nexport { defaultBodyConverters, sanitizeUrl } from './actions/body/converters'\nexport { escapeHtml } from './actions/body/escapeHtml'\nexport type {\n\tBodyContext,\n\tRichTextBodyOption,\n\tSerializeBodyArgs,\n} from './actions/body/serializeBody'\nexport { serializeBody } from './actions/body/serializeBody'\nexport { textOfBody } from './actions/body/textOfBody'\nexport { renderAllValues, renderAllValuesTable } from './actions/body/wildcards'\nexport { buildDefaultActionDefinitions, defaultActionDefinitions } from './actions/builtin'\nexport type {\n\tActionDefinition,\n\tActionRunArgs,\n\tActionValidateArgs,\n\tAnyActionDefinition,\n} from './actions/defineAction'\nexport { ActionError, defineAction } from './actions/defineAction'\nexport { INLINE_DISPATCH_DEADLINE_MS } from './actions/dispatch'\nexport type { EmailActionType, EmailRender, EmailRenderArgs } from './actions/emailRender'\nexport type {\n\tFromAddressesResolver,\n\tFromAddressOption,\n\tFromAddressSource,\n\tFromAddressSourceRegistry,\n} from './actions/fromAddresses'\nexport type {\n\tRecipientResolveArgs,\n\tRecipientSource,\n\tRecipientSourceRegistry,\n} from './actions/recipientSources'\nexport type { ActionOption, ActionRegistry, ActionsConfig } from './actions/registry'\nexport { resolveActions } from './actions/registry'\nexport type { ActionResult } from './actions/runActions'\nexport { SIGNATURE_HEADER, signPayload } from './actions/sign'\nexport type { SubmissionContextArgs, SubmissionForm } from './actions/submissionContext'\nexport type {\n\tAggregateFieldResponsesArgs,\n\tAggregateFormResponsesArgs,\n} from './aggregation/aggregateResponses'\nexport {\n\taggregateFieldResponses,\n\taggregateFormResponses,\n\tfieldHasOptions,\n} from './aggregation/aggregateResponses'\nexport { aggregateRowForField, aggregateRowsForFields } from './aggregation/aggregateRows'\nexport type {\n\tFormResultsAccess,\n\tFormResultsAccessArgs,\n\tResolveResultsRequestArgs,\n\tResolveResultsRequestResult,\n} from './aggregation/resolveResultsRequest'\nexport { resolveFormResultsRequest } from './aggregation/resolveResultsRequest'\nexport type {\n\tAggregationBucket,\n\tAggregationRow,\n\tFieldAggregation,\n\tFieldMeta,\n\tSubmissionStatusFilter,\n} from './aggregation/types'\nexport { calcExpressionOf, computeCalcFields } from './calc/computeCalcFields'\nexport type { CalcResolved } from './calc/evaluate'\nexport { calcWeightKey, evaluateCalc } from './calc/evaluate'\nexport { formatCalc } from './calc/formatCalc'\nexport type { CalcDisplayConfig } from './calc/formatCalcValue'\nexport { formatCalcValue } from './calc/formatCalcValue'\nexport type { CalcAllowed } from './calc/normalizeCalc'\nexport { normalizeCalc } from './calc/normalizeCalc'\nexport type {\n\tCalcFunction,\n\tCalcSource,\n\tCalcSourceResolveArgs,\n\tCalcWeightResolveArgs,\n} from './calc/registry'\nexport { defineCalcFunction, defineCalcSource } from './calc/registry'\nexport type { ResolveCalcContextArgs } from './calc/resolveCalcContext'\nexport { calcUsesSources, resolveCalcContext } from './calc/resolveCalcContext'\nexport type { CalcExpression } from './calc/types'\nexport type {\n\tButtonFieldsOverride,\n\tButtonsOption,\n\tDefaultButtonFields,\n} from './collections/buttonFields'\nexport {\n\tbuildDefaultButtonFields,\n\tbuildNextLabelField,\n\tbuildPrevLabelField,\n\tbuildSubmitLabelField,\n} from './collections/buttonFields'\nexport type {\n\tRedirectFieldsOverride,\n\tRedirectOption,\n\tResponseOption,\n} from './collections/redirectFields'\nexport type {\n\tDefaultSettingsFields,\n\tSettingsFieldsOverride,\n\tSettingsOption,\n} from './collections/settingsFields'\nexport { buildDefaultSettingsFields } from './collections/settingsFields'\nexport { evaluateCondition } from './conditions/evaluate'\nexport type { FieldCondition } from './conditions/types'\nexport { applyConsentStatements } from './consent/applyConsentStatements'\nexport type { ConsentProof, ConsentSnapshotMode } from './consent/captureConsent'\nexport { captureConsent } from './consent/captureConsent'\nexport type { ConsentSourcesFieldOptions } from './consent/consentSourcesField'\nexport { consentSourcesField } from './consent/consentSourcesField'\nexport type { ResolveConsentEntriesArgs } from './consent/resolveConsentEntries'\nexport { resolveConsentEntries } from './consent/resolveConsentEntries'\nexport type {\n\tConsentSourceOption,\n\tResolveConsentSourcesRequestArgs,\n\tResolveConsentSourcesRequestResult,\n} from './consent/resolveConsentSourcesRequest'\nexport { resolveConsentSourcesRequest } from './consent/resolveConsentSourcesRequest'\nexport type {\n\tConsentStatement,\n\tConsentStatements,\n\tResolveConsentStatementsArgs,\n} from './consent/resolveConsentStatements'\nexport { resolveConsentStatements } from './consent/resolveConsentStatements'\nexport type { ResolvePublishedVersionRefArgs } from './consent/resolvePublishedVersionRef'\nexport { resolvePublishedVersionRef } from './consent/resolvePublishedVersionRef'\nexport type {\n\tConsentSourceEntry,\n\tConsentSourcePage,\n\tConsentSourcesResolver,\n} from './consent/types'\nexport type { FormContextReference } from './context/formContext'\nexport { signFormContext, verifyFormContext } from './context/formContext'\nexport type {\n\tDepartmentEmailsResolver,\n\tDepartmentOption,\n\tResolveDepartmentOptionsArgs,\n\tResolveDepartmentsRequestArgs,\n\tResolveDepartmentsRequestResult,\n} from './email/departments'\nexport { resolveDepartmentOptions } from './email/departments'\nexport type { DepartmentsFieldOptions } from './email/departmentsField'\nexport { departmentsField } from './email/departmentsField'\nexport {\n\tbuildDefaultFieldDefinitions,\n\tdefaultFieldDefinitions,\n\tdefaultFieldDefinitionsByType,\n} from './fields/builtin'\nexport { countryField } from './fields/builtin/country'\nexport { fileMimeTypeOptions } from './fields/builtin/file'\nexport { stateField } from './fields/builtin/state'\nexport { defineFormField } from './fields/defineFormField'\nexport { fieldKey } from './fields/fieldKey'\nexport { localizedIf } from './fields/localizedIf'\nexport type { FieldTypeOption, FieldTypeRegistry, FieldTypesConfig } from './fields/registry'\nexport type {\n\tAnyFormFieldDefinition,\n\tFormFieldDefinition,\n\tFormFieldFormat,\n\tFormFieldValidate,\n\tFormFieldValueKind,\n\tOmittableSharedField,\n\tResolveFieldOptionsArgs,\n} from './fields/types'\nexport type {\n\tFormFallbackLocale,\n\tFormFallbackLocaleArgs,\n\tFormFallbackLocaleResult,\n} from './form/findFormAtLocale'\nexport { isPollClosed } from './form/pollState'\nexport type { ToFormDocumentOptions } from './form/toFormDocument'\nexport { toFormDocument } from './form/toFormDocument'\nexport type {\n\tFormButtonSettings,\n\tFormDocument,\n\tFormPollSettings,\n\tFormResponseSettings,\n} from './form/types'\nexport type {\n\tFormBuilderPluginOptions,\n\tFormBuilderPluginOptions as PluginOptions,\n} from './options'\nexport type { FormBuilderCollectionSlugs } from './plugin/collectionSlugs'\nexport {\n\tcollectionSlugsOf,\n\tDEFAULT_COLLECTION_SLUGS,\n\tDEFAULT_VOTED_COOKIE_PREFIX,\n} from './plugin/collectionSlugs'\nexport type { UploadsOption } from './plugin/uploadsCollection'\nexport type { PollCloseTaskInput } from './poll/closeJob'\nexport {\n\tbuildPollCloseTask,\n\tenqueuePollClose,\n\tPOLL_CLOSE_TASK_SLUG,\n\tregisterPollCloseTask,\n\trunPollClose,\n\tshouldAutoResolvePoll,\n} from './poll/closeJob'\nexport type {\n\tAnyPollOptionSource,\n\tPollOption,\n\tPollOptionResolveArgs,\n\tPollOptionSource,\n} from './poll/definePollOptionSource'\nexport { definePollOptionSource } from './poll/definePollOptionSource'\nexport type { PollOutcomeStrategy, PollOutcomeStrategyArgs } from './poll/definePollType'\nexport { definePollType } from './poll/definePollType'\nexport type { ResolveEffectivePollOptionsArgs } from './poll/effectivePollOptions'\nexport { resolveEffectivePollOptions } from './poll/effectivePollOptions'\nexport { mostVotedStrategy, topBucketValues } from './poll/mostVoted'\nexport type { DefaultOutcomeFields, OutcomeFieldsOverride } from './poll/outcomeFields'\nexport {\n\tbuildDefaultOutcomeFields,\n\tbuildResolvedAtField,\n\tbuildWinningValuesField,\n} from './poll/outcomeFields'\nexport type { PollTypeRegistry, PollTypesConfig } from './poll/pollTypeRegistry'\nexport {\n\tmanualStrategy,\n\tpollTypesOf,\n\tresolvePollTypes,\n\tsourceStrategy,\n\tstashPollTypes,\n} from './poll/pollTypeRegistry'\nexport type {\n\tPollOptionSourceOption,\n\tPollOptionSourceRegistry,\n\tPollOptionSourcesConfig,\n} from './poll/registry'\nexport { resolvePollOptionSources } from './poll/registry'\nexport type { ResolvePollOptionsArgs } from './poll/resolvePollOptions'\nexport { resolvePollOptions } from './poll/resolvePollOptions'\nexport type { ResolvePollOutcomeArgs } from './poll/resolvePollOutcome'\nexport { resolvePollOutcome } from './poll/resolvePollOutcome'\nexport { aggregateFromVotes } from './poll/votes/aggregateFromVotes'\nexport { recountPollVotes } from './poll/votes/recountPollVotes'\nexport { POLL_VOTES_SLUG, RESPONDENTS_VALUE, VOTE_SHARDS } from './poll/votes/votesCollection'\nexport type { PrefillOptions } from './prefill/valuesFromSearchParams'\nexport { valuesFromSearchParams } from './prefill/valuesFromSearchParams'\nexport { DEFAULT_PRESENTATION_NAME, defaultPresentationDescriptors } from './presentations/defaults'\nexport type {\n\tPresentationDensity,\n\tPresentationDescriptor,\n\tPresentationSurface,\n} from './presentations/types'\nexport { interpolate } from './recall/interpolate'\nexport type { RecallResolver } from './recall/resolver'\nexport { buildRecallResolver, optionLabelsFor } from './recall/resolver'\nexport { defineCaptchaProvider } from './spam/captcha'\nexport { CAPTCHA_TOKEN_KEY, DEFAULT_HONEYPOT_FIELD } from './spam/constants'\nexport { defaultIdentify } from './spam/identify'\nexport type { HcaptchaProviderOptions } from './spam/providers/hcaptcha'\nexport { hcaptchaProvider } from './spam/providers/hcaptcha'\nexport type { RecaptchaProviderOptions } from './spam/providers/recaptcha'\nexport { recaptchaProvider } from './spam/providers/recaptcha'\nexport type { TurnstileProviderOptions } from './spam/providers/turnstile'\nexport { turnstileProvider } from './spam/providers/turnstile'\nexport { createKvRateLimiter } from './spam/rateLimiter'\nexport { resolveSpamConfig } from './spam/resolveSpam'\nexport type {\n\tCaptchaProvider,\n\tCaptchaVerifyArgs,\n\tIdentifyFn,\n\tRateLimitCheckArgs,\n\tRateLimitConfig,\n\tRateLimiter,\n\tRateLimitResult,\n\tSpamConfig,\n\tSpamMetadataConfig,\n\tSpamOption,\n} from './spam/types'\nexport type { CreatedSubmission, CreateSubmissionArgs } from './submissions/createSubmission'\nexport { createSubmission } from './submissions/createSubmission'\nexport type { VotedSubmission } from './submissions/resolveVotedSubmission'\nexport { resolveVotedSubmission } from './submissions/resolveVotedSubmission'\nexport { hasVotedCookie, votedCookieName } from './submissions/votedCookie'\nexport { captureFileRef } from './uploads/captureFileRef'\nexport { formatBytes } from './uploads/formatBytes'\nexport { resolveFileRef } from './uploads/resolveFileRef'\nexport type { FileFieldConfig, FileRef, FileRefError } from './uploads/types'\nexport { defaultValidationRules, defaultValidationRulesByType } from './validation/builtin'\nexport { defineValidationRule } from './validation/defineValidationRule'\nexport type { FieldTargetParamOptions } from './validation/fieldTargetParam'\nexport { fieldTargetParam } from './validation/fieldTargetParam'\nexport type {\n\tValidationRuleOption,\n\tValidationRuleRegistry,\n\tValidationRulesConfig,\n} from './validation/registry'\nexport type {\n\tAnyValidationRuleDefinition,\n\tValidationRuleDefinition,\n\tValidationRuleResult,\n} from './validation/types'\n"],"mappings":"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"}
package/dist/options.d.ts CHANGED
@@ -282,6 +282,14 @@ type FormBuilderPluginOptions = {
282
282
  */
283
283
  poll?: {
284
284
  votedCookie?: boolean;
285
+ /**
286
+ * Name prefix of the httpOnly voted cookie (default `fb-voted-`, so `fb-voted-{formId}`), for
287
+ * hosts with cookie naming or consent-classification rules. Applies to both the `votedCookie`
288
+ * marker and the signed `allowChange` cookie. Must be a valid cookie-name token; validated at
289
+ * boot. Pass `payload` to `hasVotedCookie`/`votedCookieName` so they read the same name.
290
+ * Changing it on a live site orphans every cookie already issued.
291
+ */
292
+ cookiePrefix?: string;
285
293
  sources?: PollOptionSourcesConfig;
286
294
  types?: PollTypesConfig;
287
295
  outcomeFields?: OutcomeFieldsOverride;
@@ -290,7 +298,7 @@ type FormBuilderPluginOptions = {
290
298
  * collection of aggregate rows at submit time, so results reads are O(options), never
291
299
  * truncate, and survive `persistSubmissions: false`. `false` restores scan-based results
292
300
  * (and then a persist-off poll is rejected at save). `overrides` opens the tally
293
- * collection (slug stays `form-poll-votes`).
301
+ * collection, including its `slug` (default `form-poll-votes`).
294
302
  */
295
303
  votes?: false | {
296
304
  overrides?: CollectionOverrides;
@@ -308,6 +316,12 @@ type FormBuilderPluginOptions = {
308
316
  * order per key determines who wins), hooks are appended after the plugin's own hooks, and
309
317
  * `fields` is a function that receives the default fields and returns the final array so
310
318
  * additions/removals are always intentional.
319
+ *
320
+ * `slug` renames a collection (defaults `forms` and `form-submissions`); the plugin resolves it
321
+ * at boot and uses it everywhere, including relationships, jobs, and the voted cookie. Browser
322
+ * components keep posting to the default REST paths until handed the same slugs through
323
+ * `<Form collections>`. Read the live slugs server-side with `collectionSlugsOf(payload)`.
324
+ * Renaming on a live database is a migration: existing rows stay under the old collection.
311
325
  */
312
326
  overrides?: {
313
327
  forms?: CollectionOverrides;
@@ -0,0 +1,28 @@
1
+ import { CollectionSlug, Payload } from "payload";
2
+
3
+ //#region src/plugin/collectionSlugs.d.ts
4
+ /**
5
+ * The slugs of the collections the plugin registers, after host overrides. Typed as `CollectionSlug`
6
+ * because each one is a registered collection, so a host's generated slug union always contains it
7
+ * and the values pass straight to `payload.find({ collection })`.
8
+ */
9
+ type FormBuilderCollectionSlugs = {
10
+ forms: CollectionSlug;
11
+ formSubmissions: CollectionSlug;
12
+ pollVotes: CollectionSlug;
13
+ };
14
+ declare const DEFAULT_COLLECTION_SLUGS: Readonly<{
15
+ readonly forms: "forms";
16
+ readonly formSubmissions: "form-submissions";
17
+ readonly pollVotes: "form-poll-votes";
18
+ }>;
19
+ declare const DEFAULT_VOTED_COOKIE_PREFIX = "fb-voted-";
20
+ /**
21
+ * The collection slugs the plugin registered on this Payload instance, for code that queries the
22
+ * plugin's collections (`payload.find({ collection: collectionSlugsOf(payload).formSubmissions })`).
23
+ * The defaults when the plugin is disabled or absent.
24
+ */
25
+ declare const collectionSlugsOf: (payload: Payload) => FormBuilderCollectionSlugs;
26
+ //#endregion
27
+ export { DEFAULT_COLLECTION_SLUGS, DEFAULT_VOTED_COOKIE_PREFIX, FormBuilderCollectionSlugs, collectionSlugsOf };
28
+ //# sourceMappingURL=collectionSlugs.d.ts.map
@@ -0,0 +1,65 @@
1
+ import { customStateOf, stashCustomState } from "./customState.js";
2
+ //#region src/plugin/collectionSlugs.ts
3
+ const DEFAULT_COLLECTION_SLUGS = Object.freeze({
4
+ forms: "forms",
5
+ formSubmissions: "form-submissions",
6
+ pollVotes: "form-poll-votes"
7
+ });
8
+ const DEFAULT_VOTED_COOKIE_PREFIX = "fb-voted-";
9
+ const OPTION_PATHS = {
10
+ forms: "overrides.forms.slug",
11
+ formSubmissions: "overrides.formSubmissions.slug",
12
+ pollVotes: "poll.votes.overrides.slug"
13
+ };
14
+ const slugOf = (override, fallback) => {
15
+ const slug = override?.slug?.trim();
16
+ return slug ? slug : fallback;
17
+ };
18
+ /**
19
+ * Resolve every plugin collection slug from the `slug` each collection's override carries,
20
+ * falling back to the defaults. Throws when two plugin collections would share a slug, since
21
+ * Payload would merge them into one collection with the other's fields and hooks.
22
+ */
23
+ const resolveCollectionSlugs = (options) => {
24
+ const votes = options.poll?.votes;
25
+ const slugs = {
26
+ forms: slugOf(options.overrides?.forms, DEFAULT_COLLECTION_SLUGS.forms),
27
+ formSubmissions: slugOf(options.overrides?.formSubmissions, DEFAULT_COLLECTION_SLUGS.formSubmissions),
28
+ pollVotes: slugOf(votes === false ? void 0 : votes?.overrides, DEFAULT_COLLECTION_SLUGS.pollVotes)
29
+ };
30
+ const registered = Object.keys(slugs).filter((key) => key !== "pollVotes" || votes !== false);
31
+ for (const [index, key] of registered.entries()) {
32
+ const clash = registered.slice(index + 1).find((other) => slugs[other] === slugs[key]);
33
+ if (clash) throw new Error(`@10x-media/form-builder: ${OPTION_PATHS[key]} and ${OPTION_PATHS[clash]} both resolve to "${slugs[key]}"; each plugin collection needs its own slug.`);
34
+ }
35
+ return slugs;
36
+ };
37
+ const COOKIE_NAME_TOKEN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
38
+ /** Validate `poll.cookiePrefix` at boot, so a bad name fails loudly instead of breaking `Set-Cookie`. */
39
+ const resolveVotedCookiePrefix = (prefix) => {
40
+ if (prefix === void 0) return DEFAULT_VOTED_COOKIE_PREFIX;
41
+ if (!COOKIE_NAME_TOKEN.test(prefix)) throw new Error(`@10x-media/form-builder: poll.cookiePrefix "${prefix}" is not a valid cookie name; use letters, digits, and !#$%&'*+-.^_\`|~ only.`);
42
+ return prefix;
43
+ };
44
+ const stashCollectionSlugs = (custom, collectionSlugs, votedCookiePrefix) => stashCustomState(custom, {
45
+ collectionSlugs,
46
+ ...votedCookiePrefix === void 0 ? {} : { votedCookiePrefix }
47
+ });
48
+ /**
49
+ * The collection slugs the plugin registered on this Payload instance, for code that queries the
50
+ * plugin's collections (`payload.find({ collection: collectionSlugsOf(payload).formSubmissions })`).
51
+ * The defaults when the plugin is disabled or absent.
52
+ */
53
+ const collectionSlugsOf = (payload) => customStateOf(payload).collectionSlugs ?? { ...DEFAULT_COLLECTION_SLUGS };
54
+ /**
55
+ * The resolved slugs typed as the default literals, for the plugin's own Local API calls. A plugin
56
+ * collection keeps the plugin's document shape under any slug, which is exactly what generated types
57
+ * for the default slugs describe, so reads narrow as they did when the slugs were constants. Host
58
+ * code uses `collectionSlugsOf`, whose `string` slugs match whatever the host registered.
59
+ */
60
+ const pluginSlugsOf = (payload) => collectionSlugsOf(payload);
61
+ const votedCookiePrefixOf = (payload) => customStateOf(payload).votedCookiePrefix ?? "fb-voted-";
62
+ //#endregion
63
+ export { DEFAULT_COLLECTION_SLUGS, DEFAULT_VOTED_COOKIE_PREFIX, collectionSlugsOf, pluginSlugsOf, resolveCollectionSlugs, resolveVotedCookiePrefix, stashCollectionSlugs, votedCookiePrefixOf };
64
+
65
+ //# sourceMappingURL=collectionSlugs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collectionSlugs.js","names":[],"sources":["../../src/plugin/collectionSlugs.ts"],"sourcesContent":["import type { CollectionSlug, Payload } from 'payload'\nimport { customStateOf, stashCustomState } from './customState'\n\n/**\n * The slugs of the collections the plugin registers, after host overrides. Typed as `CollectionSlug`\n * because each one is a registered collection, so a host's generated slug union always contains it\n * and the values pass straight to `payload.find({ collection })`.\n */\nexport type FormBuilderCollectionSlugs = {\n\tforms: CollectionSlug\n\tformSubmissions: CollectionSlug\n\tpollVotes: CollectionSlug\n}\n\nexport const DEFAULT_COLLECTION_SLUGS = Object.freeze({\n\tforms: 'forms',\n\tformSubmissions: 'form-submissions',\n\tpollVotes: 'form-poll-votes',\n} as const)\n\nexport const DEFAULT_VOTED_COOKIE_PREFIX = 'fb-voted-'\n\ntype SlugOverride = { slug?: string } | undefined\n\n/** The slice of the plugin options slugs are read from; structural so this module stays a leaf. */\nexport type CollectionSlugOptions = {\n\toverrides?: { forms?: SlugOverride; formSubmissions?: SlugOverride }\n\tpoll?: { votes?: false | { overrides?: SlugOverride } }\n}\n\nconst OPTION_PATHS: Record<keyof FormBuilderCollectionSlugs, string> = {\n\tforms: 'overrides.forms.slug',\n\tformSubmissions: 'overrides.formSubmissions.slug',\n\tpollVotes: 'poll.votes.overrides.slug',\n}\n\nconst slugOf = (override: SlugOverride, fallback: string): CollectionSlug => {\n\tconst slug = override?.slug?.trim()\n\t// Registered under this very slug at boot, so it is a member of the host's generated union.\n\treturn (slug ? slug : fallback) as CollectionSlug\n}\n\n/**\n * Resolve every plugin collection slug from the `slug` each collection's override carries,\n * falling back to the defaults. Throws when two plugin collections would share a slug, since\n * Payload would merge them into one collection with the other's fields and hooks.\n */\nexport const resolveCollectionSlugs = (\n\toptions: CollectionSlugOptions\n): FormBuilderCollectionSlugs => {\n\tconst votes = options.poll?.votes\n\tconst slugs: FormBuilderCollectionSlugs = {\n\t\tforms: slugOf(options.overrides?.forms, DEFAULT_COLLECTION_SLUGS.forms),\n\t\tformSubmissions: slugOf(\n\t\t\toptions.overrides?.formSubmissions,\n\t\t\tDEFAULT_COLLECTION_SLUGS.formSubmissions\n\t\t),\n\t\tpollVotes: slugOf(\n\t\t\tvotes === false ? undefined : votes?.overrides,\n\t\t\tDEFAULT_COLLECTION_SLUGS.pollVotes\n\t\t),\n\t}\n\tconst registered = (Object.keys(slugs) as (keyof FormBuilderCollectionSlugs)[]).filter(\n\t\t(key) => key !== 'pollVotes' || votes !== false\n\t)\n\tfor (const [index, key] of registered.entries()) {\n\t\tconst clash = registered.slice(index + 1).find((other) => slugs[other] === slugs[key])\n\t\tif (clash) {\n\t\t\tthrow new Error(\n\t\t\t\t`@10x-media/form-builder: ${OPTION_PATHS[key]} and ${OPTION_PATHS[clash]} both resolve to \"${slugs[key]}\"; each plugin collection needs its own slug.`\n\t\t\t)\n\t\t}\n\t}\n\treturn slugs\n}\n\nconst COOKIE_NAME_TOKEN = /^[!#$%&'*+\\-.^_`|~0-9A-Za-z]+$/\n\n/** Validate `poll.cookiePrefix` at boot, so a bad name fails loudly instead of breaking `Set-Cookie`. */\nexport const resolveVotedCookiePrefix = (prefix: string | undefined): string => {\n\tif (prefix === undefined) {\n\t\treturn DEFAULT_VOTED_COOKIE_PREFIX\n\t}\n\tif (!COOKIE_NAME_TOKEN.test(prefix)) {\n\t\tthrow new Error(\n\t\t\t`@10x-media/form-builder: poll.cookiePrefix \"${prefix}\" is not a valid cookie name; use letters, digits, and !#$%&'*+-.^_\\`|~ only.`\n\t\t)\n\t}\n\treturn prefix\n}\n\ntype SlugState = { collectionSlugs: FormBuilderCollectionSlugs; votedCookiePrefix?: string }\n\nexport const stashCollectionSlugs = (\n\tcustom: Record<string, unknown> | undefined,\n\tcollectionSlugs: FormBuilderCollectionSlugs,\n\tvotedCookiePrefix?: string\n): Record<string, unknown> =>\n\tstashCustomState<SlugState>(custom, {\n\t\tcollectionSlugs,\n\t\t...(votedCookiePrefix === undefined ? {} : { votedCookiePrefix }),\n\t})\n\n/**\n * The collection slugs the plugin registered on this Payload instance, for code that queries the\n * plugin's collections (`payload.find({ collection: collectionSlugsOf(payload).formSubmissions })`).\n * The defaults when the plugin is disabled or absent.\n */\nexport const collectionSlugsOf = (payload: Payload): FormBuilderCollectionSlugs =>\n\tcustomStateOf<SlugState>(payload).collectionSlugs ?? { ...DEFAULT_COLLECTION_SLUGS }\n\n/**\n * The resolved slugs typed as the default literals, for the plugin's own Local API calls. A plugin\n * collection keeps the plugin's document shape under any slug, which is exactly what generated types\n * for the default slugs describe, so reads narrow as they did when the slugs were constants. Host\n * code uses `collectionSlugsOf`, whose `string` slugs match whatever the host registered.\n */\nexport const pluginSlugsOf = (payload: Payload): typeof DEFAULT_COLLECTION_SLUGS =>\n\tcollectionSlugsOf(payload) as unknown as typeof DEFAULT_COLLECTION_SLUGS\n\nexport const votedCookiePrefixOf = (payload: Payload): string =>\n\tcustomStateOf<SlugState>(payload).votedCookiePrefix ?? DEFAULT_VOTED_COOKIE_PREFIX\n"],"mappings":";;AAcA,MAAa,2BAA2B,OAAO,OAAO;CACrD,OAAO;CACP,iBAAiB;CACjB,WAAW;AACZ,CAAU;AAEV,MAAa,8BAA8B;AAU3C,MAAM,eAAiE;CACtE,OAAO;CACP,iBAAiB;CACjB,WAAW;AACZ;AAEA,MAAM,UAAU,UAAwB,aAAqC;CAC5E,MAAM,OAAO,UAAU,MAAM,KAAK;CAElC,OAAQ,OAAO,OAAO;AACvB;;;;;;AAOA,MAAa,0BACZ,YACgC;CAChC,MAAM,QAAQ,QAAQ,MAAM;CAC5B,MAAM,QAAoC;EACzC,OAAO,OAAO,QAAQ,WAAW,OAAO,yBAAyB,KAAK;EACtE,iBAAiB,OAChB,QAAQ,WAAW,iBACnB,yBAAyB,eAC1B;EACA,WAAW,OACV,UAAU,QAAQ,KAAA,IAAY,OAAO,WACrC,yBAAyB,SAC1B;CACD;CACA,MAAM,aAAc,OAAO,KAAK,KAAK,EAA2C,QAC9E,QAAQ,QAAQ,eAAe,UAAU,KAC3C;CACA,KAAK,MAAM,CAAC,OAAO,QAAQ,WAAW,QAAQ,GAAG;EAChD,MAAM,QAAQ,WAAW,MAAM,QAAQ,CAAC,EAAE,MAAM,UAAU,MAAM,WAAW,MAAM,IAAI;EACrF,IAAI,OACH,MAAM,IAAI,MACT,4BAA4B,aAAa,KAAK,OAAO,aAAa,OAAO,oBAAoB,MAAM,KAAK,8CACzG;CAEF;CACA,OAAO;AACR;AAEA,MAAM,oBAAoB;;AAG1B,MAAa,4BAA4B,WAAuC;CAC/E,IAAI,WAAW,KAAA,GACd,OAAO;CAER,IAAI,CAAC,kBAAkB,KAAK,MAAM,GACjC,MAAM,IAAI,MACT,+CAA+C,OAAO,8EACvD;CAED,OAAO;AACR;AAIA,MAAa,wBACZ,QACA,iBACA,sBAEA,iBAA4B,QAAQ;CACnC;CACA,GAAI,sBAAsB,KAAA,IAAY,CAAC,IAAI,EAAE,kBAAkB;AAChE,CAAC;;;;;;AAOF,MAAa,qBAAqB,YACjC,cAAyB,OAAO,EAAE,mBAAmB,EAAE,GAAG,yBAAyB;;;;;;;AAQpF,MAAa,iBAAiB,YAC7B,kBAAkB,OAAO;AAE1B,MAAa,uBAAuB,YACnC,cAAyB,OAAO,EAAE,qBAAA"}
@@ -19,7 +19,7 @@ const stashCustomState = (custom, patch) => {
19
19
  };
20
20
  };
21
21
  /** The plugin's slice of `payload.config.custom`, or an empty object when the plugin never booted. */
22
- const customStateOf = (payload) => payload.config.custom?.[CUSTOM_KEY] ?? {};
22
+ const customStateOf = (payload) => payload.config?.custom?.[CUSTOM_KEY] ?? {};
23
23
  //#endregion
24
24
  export { customStateOf, stashCustomState };
25
25
 
@@ -1 +1 @@
1
- {"version":3,"file":"customState.js","names":[],"sources":["../../src/plugin/customState.ts"],"sourcesContent":["import type { Payload } from 'payload'\n\n/** Root key under `config.custom` where the plugin stashes runtime state for root-level helpers. */\nconst CUSTOM_KEY = '@10x-media/form-builder'\n\n/**\n * Merge `patch` into the plugin's slice of `config.custom`, leaving every other key untouched.\n * Root-level helpers (`resolvePollOptions`, `resolveConsentStatements`) are called by hosts with a\n * `payload` instance and no plugin state, so the plugin parks what they need on the config at boot\n * and they read it back through `payload.config`. Both readers share one key, so the plugin's slice\n * is patched rather than replaced.\n */\nexport const stashCustomState = <TState extends Record<string, unknown>>(\n\tcustom: Record<string, unknown> | undefined,\n\tpatch: TState\n): Record<string, unknown> => {\n\tconst existing =\n\t\tcustom?.[CUSTOM_KEY] != null && typeof custom[CUSTOM_KEY] === 'object'\n\t\t\t? (custom[CUSTOM_KEY] as Record<string, unknown>)\n\t\t\t: {}\n\treturn { ...(custom ?? {}), [CUSTOM_KEY]: { ...existing, ...patch } }\n}\n\n/** The plugin's slice of `payload.config.custom`, or an empty object when the plugin never booted. */\nexport const customStateOf = <TState extends Record<string, unknown>>(\n\tpayload: Payload\n): Partial<TState> => (payload.config.custom?.[CUSTOM_KEY] as Partial<TState> | undefined) ?? {}\n"],"mappings":";;AAGA,MAAM,aAAa;;;;;;;;AASnB,MAAa,oBACZ,QACA,UAC6B;CAC7B,MAAM,WACL,SAAS,eAAe,QAAQ,OAAO,OAAO,gBAAgB,WAC1D,OAAO,cACR,CAAC;CACL,OAAO;EAAE,GAAI,UAAU,CAAC;GAAK,aAAa;GAAE,GAAG;GAAU,GAAG;EAAM;CAAE;AACrE;;AAGA,MAAa,iBACZ,YACsB,QAAQ,OAAO,SAAS,eAA+C,CAAC"}
1
+ {"version":3,"file":"customState.js","names":[],"sources":["../../src/plugin/customState.ts"],"sourcesContent":["import type { Payload } from 'payload'\n\n/** Root key under `config.custom` where the plugin stashes runtime state for root-level helpers. */\nconst CUSTOM_KEY = '@10x-media/form-builder'\n\n/**\n * Merge `patch` into the plugin's slice of `config.custom`, leaving every other key untouched.\n * Root-level helpers (`resolvePollOptions`, `resolveConsentStatements`) are called by hosts with a\n * `payload` instance and no plugin state, so the plugin parks what they need on the config at boot\n * and they read it back through `payload.config`. Both readers share one key, so the plugin's slice\n * is patched rather than replaced.\n */\nexport const stashCustomState = <TState extends Record<string, unknown>>(\n\tcustom: Record<string, unknown> | undefined,\n\tpatch: TState\n): Record<string, unknown> => {\n\tconst existing =\n\t\tcustom?.[CUSTOM_KEY] != null && typeof custom[CUSTOM_KEY] === 'object'\n\t\t\t? (custom[CUSTOM_KEY] as Record<string, unknown>)\n\t\t\t: {}\n\treturn { ...(custom ?? {}), [CUSTOM_KEY]: { ...existing, ...patch } }\n}\n\n/** The plugin's slice of `payload.config.custom`, or an empty object when the plugin never booted. */\nexport const customStateOf = <TState extends Record<string, unknown>>(\n\tpayload: Payload\n): Partial<TState> => (payload.config?.custom?.[CUSTOM_KEY] as Partial<TState> | undefined) ?? {}\n"],"mappings":";;AAGA,MAAM,aAAa;;;;;;;;AASnB,MAAa,oBACZ,QACA,UAC6B;CAC7B,MAAM,WACL,SAAS,eAAe,QAAQ,OAAO,OAAO,gBAAgB,WAC1D,OAAO,cACR,CAAC;CACL,OAAO;EAAE,GAAI,UAAU,CAAC;GAAK,aAAa;GAAE,GAAG;GAAU,GAAG;EAAM;CAAE;AACrE;;AAGA,MAAa,iBACZ,YACsB,QAAQ,QAAQ,SAAS,eAA+C,CAAC"}
@@ -5,7 +5,7 @@ import { registerPollCloseTask } from "../poll/closeJob.js";
5
5
  import { buildFormsCollection } from "../collections/forms.js";
6
6
  import { attachUploadsCollection } from "./uploadsCollection.js";
7
7
  //#region src/plugin/registerCollections.ts
8
- const registerCollections = ({ config, registry, ruleRegistry, calcAllowed, calcSources, calcFunctions, consentSources, consentSnapshot, consentResolveOnRead, actionRegistry, richText, hasJobsPlugin, dispatchDeadlineMs, events, uploads, spam, showSubmissionRawFields, localizeContent, resultsAccess, votedCookie, pollSourceRegistry, pollTypeRegistry, outcomeFields, pollVotes, buttons, settings, response, fromAddresses, fromSources, departments, redirectRelationships, overrides }) => {
8
+ const registerCollections = ({ config, registry, ruleRegistry, calcAllowed, calcSources, calcFunctions, consentSources, consentSnapshot, consentResolveOnRead, actionRegistry, richText, hasJobsPlugin, dispatchDeadlineMs, events, uploads, spam, showSubmissionRawFields, localizeContent, resultsAccess, votedCookie, pollSourceRegistry, pollTypeRegistry, outcomeFields, pollVotes, buttons, settings, response, fromAddresses, fromSources, departments, redirectRelationships, slugs, overrides }) => {
9
9
  const pollVotesEnabled = pollVotes !== false;
10
10
  registerActionsTask(config, actionRegistry, richText);
11
11
  registerPollCloseTask(config, pollVotesEnabled);
@@ -41,6 +41,7 @@ const registerCollections = ({ config, registry, ruleRegistry, calcAllowed, calc
41
41
  fromSources,
42
42
  departments,
43
43
  redirectRelationships,
44
+ slugs,
44
45
  overrides: overrides?.forms
45
46
  }),
46
47
  buildSubmissionsCollection({
@@ -61,9 +62,13 @@ const registerCollections = ({ config, registry, ruleRegistry, calcAllowed, calc
61
62
  pollSourceRegistry,
62
63
  pollVotes,
63
64
  showRawFields: showSubmissionRawFields,
65
+ slugs,
64
66
  overrides: overrides?.formSubmissions
65
67
  }),
66
- ...pollVotes === false ? [] : [buildPollVotesCollection({ overrides: pollVotes.overrides })]
68
+ ...pollVotes === false ? [] : [buildPollVotesCollection({
69
+ overrides: pollVotes.overrides,
70
+ slug: slugs.pollVotes
71
+ })]
67
72
  ];
68
73
  };
69
74
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"registerCollections.js","names":[],"sources":["../../src/plugin/registerCollections.ts"],"sourcesContent":["import type { CollectionSlug, Config } from 'payload'\nimport type { RichTextBodyOption } from '../actions/body/serializeBody'\nimport type { FromAddressesResolver, FromAddressSourceRegistry } from '../actions/fromAddresses'\nimport type { ActionRegistry } from '../actions/registry'\nimport { registerActionsTask } from '../actions/task'\nimport type { FormResultsAccess } from '../aggregation/resolveResultsRequest'\nimport type { CalcAllowed } from '../calc/normalizeCalc'\nimport type { CalcFunction, CalcSource } from '../calc/registry'\nimport type { ButtonsOption } from '../collections/buttonFields'\nimport { buildSubmissionsCollection } from '../collections/formSubmissions'\nimport { buildFormsCollection } from '../collections/forms'\nimport type { ResponseOption } from '../collections/redirectFields'\nimport type { SettingsOption } from '../collections/settingsFields'\nimport type { ConsentSnapshotMode } from '../consent/captureConsent'\nimport type { ConsentSourcesResolver } from '../consent/types'\nimport type { DepartmentEmailsResolver } from '../email/departments'\nimport type { FormEventSink } from '../events/types'\nimport type { FieldTypeRegistry } from '../fields/registry'\nimport { registerPollCloseTask } from '../poll/closeJob'\nimport type { OutcomeFieldsOverride } from '../poll/outcomeFields'\nimport type { PollTypeRegistry } from '../poll/pollTypeRegistry'\nimport type { PollOptionSourceRegistry } from '../poll/registry'\nimport { buildPollVotesCollection } from '../poll/votes/votesCollection'\nimport type { ResolvedSpamConfig } from '../spam/types'\nimport type { ValidationRuleRegistry } from '../validation/registry'\nimport type { CollectionOverrides } from './collectionOverrides'\nimport { attachUploadsCollection, type UploadsOption } from './uploadsCollection'\n\ntype RegisterCollectionsArgs = {\n\tconfig: Config\n\tregistry: FieldTypeRegistry\n\truleRegistry: ValidationRuleRegistry\n\t/** Registered calc extension names; gates which expressions the forms beforeValidate accepts. */\n\tcalcAllowed?: CalcAllowed\n\t/** Registered calc sources (plugin option `calc.sources`); resolved on form reads and at submit. */\n\tcalcSources?: Record<string, CalcSource>\n\t/** Registered calc functions (plugin option `calc.functions`); threaded into submit-time evaluation. */\n\tcalcFunctions?: Record<string, CalcFunction>\n\tconsentSources?: ConsentSourcesResolver\n\tconsentSnapshot?: ConsentSnapshotMode\n\t/** Plugin `consent.resolveOnRead` (default true); false skips the per-read consent afterRead hook. */\n\tconsentResolveOnRead?: boolean\n\tactionRegistry: ActionRegistry\n\trichText?: RichTextBodyOption\n\thasJobsPlugin: boolean\n\t/** Host override for the inline dispatch deadline (plugin option `dispatch.deadlineMs`). */\n\tdispatchDeadlineMs?: number\n\tevents?: FormEventSink\n\tuploads: UploadsOption\n\tspam: ResolvedSpamConfig | false\n\tshowSubmissionRawFields: boolean\n\tlocalizeContent: boolean\n\tresultsAccess?: FormResultsAccess\n\tvotedCookie: boolean\n\tpollSourceRegistry: PollOptionSourceRegistry\n\tpollTypeRegistry: PollTypeRegistry\n\toutcomeFields?: OutcomeFieldsOverride\n\t/** Resolved `poll.votes` option; `false` skips registering the hidden tally collection. */\n\tpollVotes: false | { overrides?: CollectionOverrides }\n\tbuttons?: ButtonsOption\n\tsettings?: SettingsOption\n\tresponse?: ResponseOption\n\tfromAddresses?: FromAddressesResolver\n\tfromSources?: FromAddressSourceRegistry\n\tdepartments?: DepartmentEmailsResolver\n\tredirectRelationships?: CollectionSlug[]\n\toverrides?: {\n\t\tforms?: CollectionOverrides\n\t\tformSubmissions?: CollectionOverrides\n\t}\n}\n\nexport const registerCollections = ({\n\tconfig,\n\tregistry,\n\truleRegistry,\n\tcalcAllowed,\n\tcalcSources,\n\tcalcFunctions,\n\tconsentSources,\n\tconsentSnapshot,\n\tconsentResolveOnRead,\n\tactionRegistry,\n\trichText,\n\thasJobsPlugin,\n\tdispatchDeadlineMs,\n\tevents,\n\tuploads,\n\tspam,\n\tshowSubmissionRawFields,\n\tlocalizeContent,\n\tresultsAccess,\n\tvotedCookie,\n\tpollSourceRegistry,\n\tpollTypeRegistry,\n\toutcomeFields,\n\tpollVotes,\n\tbuttons,\n\tsettings,\n\tresponse,\n\tfromAddresses,\n\tfromSources,\n\tdepartments,\n\tredirectRelationships,\n\toverrides,\n}: RegisterCollectionsArgs): void => {\n\tconst pollVotesEnabled = pollVotes !== false\n\tregisterActionsTask(config, actionRegistry, richText)\n\tregisterPollCloseTask(config, pollVotesEnabled)\n\tconst hasRunner = Boolean(config.jobs?.autoRun) || hasJobsPlugin\n\n\tconst uploadSlug = uploads === false ? undefined : uploads.collection\n\tif (uploads !== false) {\n\t\tattachUploadsCollection({ config, slug: uploads.collection, spam })\n\t}\n\n\t// Payload lists nav entries in registration order (no nav-sort option). The primary\n\t// collection leads: forms, then form-submissions. Ordering the plugin's nav group against\n\t// host collections (including a BYO uploads collection) stays a host concern.\n\tconfig.collections = [\n\t\t...(config.collections ?? []),\n\t\tbuildFormsCollection({\n\t\t\tregistry,\n\t\t\truleRegistry,\n\t\t\tcalcAllowed,\n\t\t\tcalcSources,\n\t\t\tconsentSources,\n\t\t\tconsentResolveOnRead,\n\t\t\tactionRegistry,\n\t\t\tlocalizeContent,\n\t\t\trichText,\n\t\t\tuploadsCollectionSlug: uploadSlug,\n\t\t\tresultsAccess,\n\t\t\tpollVotesEnabled,\n\t\t\tpollSourceRegistry,\n\t\t\tpollTypeRegistry,\n\t\t\toutcomeFields,\n\t\t\tbuttons,\n\t\t\tsettings,\n\t\t\tresponse,\n\t\t\tfromAddresses,\n\t\t\tfromSources,\n\t\t\tdepartments,\n\t\t\tredirectRelationships,\n\t\t\toverrides: overrides?.forms,\n\t\t}),\n\t\tbuildSubmissionsCollection({\n\t\t\tregistry,\n\t\t\truleRegistry,\n\t\t\tcalcSources,\n\t\t\tcalcFunctions,\n\t\t\tconsentSources,\n\t\t\tconsentSnapshot,\n\t\t\tactionRegistry,\n\t\t\trichText,\n\t\t\tevents,\n\t\t\thasRunner,\n\t\t\tdispatchDeadlineMs,\n\t\t\tuploadSlug,\n\t\t\tspam,\n\t\t\tvotedCookie,\n\t\t\tpollSourceRegistry,\n\t\t\tpollVotes,\n\t\t\tshowRawFields: showSubmissionRawFields,\n\t\t\toverrides: overrides?.formSubmissions,\n\t\t}),\n\t\t...(pollVotes === false ? [] : [buildPollVotesCollection({ overrides: pollVotes.overrides })]),\n\t]\n}\n"],"mappings":";;;;;;;AAwEA,MAAa,uBAAuB,EACnC,QACA,UACA,cACA,aACA,aACA,eACA,gBACA,iBACA,sBACA,gBACA,UACA,eACA,oBACA,QACA,SACA,MACA,yBACA,iBACA,eACA,aACA,oBACA,kBACA,eACA,WACA,SACA,UACA,UACA,eACA,aACA,aACA,uBACA,gBACoC;CACpC,MAAM,mBAAmB,cAAc;CACvC,oBAAoB,QAAQ,gBAAgB,QAAQ;CACpD,sBAAsB,QAAQ,gBAAgB;CAC9C,MAAM,YAAY,QAAQ,OAAO,MAAM,OAAO,KAAK;CAEnD,MAAM,aAAa,YAAY,QAAQ,KAAA,IAAY,QAAQ;CAC3D,IAAI,YAAY,OACf,wBAAwB;EAAE;EAAQ,MAAM,QAAQ;EAAY;CAAK,CAAC;CAMnE,OAAO,cAAc;EACpB,GAAI,OAAO,eAAe,CAAC;EAC3B,qBAAqB;GACpB;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA,uBAAuB;GACvB;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA,WAAW,WAAW;EACvB,CAAC;EACD,2BAA2B;GAC1B;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA,eAAe;GACf,WAAW,WAAW;EACvB,CAAC;EACD,GAAI,cAAc,QAAQ,CAAC,IAAI,CAAC,yBAAyB,EAAE,WAAW,UAAU,UAAU,CAAC,CAAC;CAC7F;AACD"}
1
+ {"version":3,"file":"registerCollections.js","names":[],"sources":["../../src/plugin/registerCollections.ts"],"sourcesContent":["import type { CollectionSlug, Config } from 'payload'\nimport type { RichTextBodyOption } from '../actions/body/serializeBody'\nimport type { FromAddressesResolver, FromAddressSourceRegistry } from '../actions/fromAddresses'\nimport type { ActionRegistry } from '../actions/registry'\nimport { registerActionsTask } from '../actions/task'\nimport type { FormResultsAccess } from '../aggregation/resolveResultsRequest'\nimport type { CalcAllowed } from '../calc/normalizeCalc'\nimport type { CalcFunction, CalcSource } from '../calc/registry'\nimport type { ButtonsOption } from '../collections/buttonFields'\nimport { buildSubmissionsCollection } from '../collections/formSubmissions'\nimport { buildFormsCollection } from '../collections/forms'\nimport type { ResponseOption } from '../collections/redirectFields'\nimport type { SettingsOption } from '../collections/settingsFields'\nimport type { ConsentSnapshotMode } from '../consent/captureConsent'\nimport type { ConsentSourcesResolver } from '../consent/types'\nimport type { DepartmentEmailsResolver } from '../email/departments'\nimport type { FormEventSink } from '../events/types'\nimport type { FieldTypeRegistry } from '../fields/registry'\nimport { registerPollCloseTask } from '../poll/closeJob'\nimport type { OutcomeFieldsOverride } from '../poll/outcomeFields'\nimport type { PollTypeRegistry } from '../poll/pollTypeRegistry'\nimport type { PollOptionSourceRegistry } from '../poll/registry'\nimport { buildPollVotesCollection } from '../poll/votes/votesCollection'\nimport type { ResolvedSpamConfig } from '../spam/types'\nimport type { ValidationRuleRegistry } from '../validation/registry'\nimport type { CollectionOverrides } from './collectionOverrides'\nimport type { FormBuilderCollectionSlugs } from './collectionSlugs'\nimport { attachUploadsCollection, type UploadsOption } from './uploadsCollection'\n\ntype RegisterCollectionsArgs = {\n\tconfig: Config\n\tregistry: FieldTypeRegistry\n\truleRegistry: ValidationRuleRegistry\n\t/** Registered calc extension names; gates which expressions the forms beforeValidate accepts. */\n\tcalcAllowed?: CalcAllowed\n\t/** Registered calc sources (plugin option `calc.sources`); resolved on form reads and at submit. */\n\tcalcSources?: Record<string, CalcSource>\n\t/** Registered calc functions (plugin option `calc.functions`); threaded into submit-time evaluation. */\n\tcalcFunctions?: Record<string, CalcFunction>\n\tconsentSources?: ConsentSourcesResolver\n\tconsentSnapshot?: ConsentSnapshotMode\n\t/** Plugin `consent.resolveOnRead` (default true); false skips the per-read consent afterRead hook. */\n\tconsentResolveOnRead?: boolean\n\tactionRegistry: ActionRegistry\n\trichText?: RichTextBodyOption\n\thasJobsPlugin: boolean\n\t/** Host override for the inline dispatch deadline (plugin option `dispatch.deadlineMs`). */\n\tdispatchDeadlineMs?: number\n\tevents?: FormEventSink\n\tuploads: UploadsOption\n\tspam: ResolvedSpamConfig | false\n\tshowSubmissionRawFields: boolean\n\tlocalizeContent: boolean\n\tresultsAccess?: FormResultsAccess\n\tvotedCookie: boolean\n\tpollSourceRegistry: PollOptionSourceRegistry\n\tpollTypeRegistry: PollTypeRegistry\n\toutcomeFields?: OutcomeFieldsOverride\n\t/** Resolved `poll.votes` option; `false` skips registering the hidden tally collection. */\n\tpollVotes: false | { overrides?: CollectionOverrides }\n\tbuttons?: ButtonsOption\n\tsettings?: SettingsOption\n\tresponse?: ResponseOption\n\tfromAddresses?: FromAddressesResolver\n\tfromSources?: FromAddressSourceRegistry\n\tdepartments?: DepartmentEmailsResolver\n\tredirectRelationships?: CollectionSlug[]\n\t/** Resolved plugin collection slugs; see `resolveCollectionSlugs`. */\n\tslugs: FormBuilderCollectionSlugs\n\toverrides?: {\n\t\tforms?: CollectionOverrides\n\t\tformSubmissions?: CollectionOverrides\n\t}\n}\n\nexport const registerCollections = ({\n\tconfig,\n\tregistry,\n\truleRegistry,\n\tcalcAllowed,\n\tcalcSources,\n\tcalcFunctions,\n\tconsentSources,\n\tconsentSnapshot,\n\tconsentResolveOnRead,\n\tactionRegistry,\n\trichText,\n\thasJobsPlugin,\n\tdispatchDeadlineMs,\n\tevents,\n\tuploads,\n\tspam,\n\tshowSubmissionRawFields,\n\tlocalizeContent,\n\tresultsAccess,\n\tvotedCookie,\n\tpollSourceRegistry,\n\tpollTypeRegistry,\n\toutcomeFields,\n\tpollVotes,\n\tbuttons,\n\tsettings,\n\tresponse,\n\tfromAddresses,\n\tfromSources,\n\tdepartments,\n\tredirectRelationships,\n\tslugs,\n\toverrides,\n}: RegisterCollectionsArgs): void => {\n\tconst pollVotesEnabled = pollVotes !== false\n\tregisterActionsTask(config, actionRegistry, richText)\n\tregisterPollCloseTask(config, pollVotesEnabled)\n\tconst hasRunner = Boolean(config.jobs?.autoRun) || hasJobsPlugin\n\n\tconst uploadSlug = uploads === false ? undefined : uploads.collection\n\tif (uploads !== false) {\n\t\tattachUploadsCollection({ config, slug: uploads.collection, spam })\n\t}\n\n\t// Payload lists nav entries in registration order (no nav-sort option). The primary\n\t// collection leads: forms, then form-submissions. Ordering the plugin's nav group against\n\t// host collections (including a BYO uploads collection) stays a host concern.\n\tconfig.collections = [\n\t\t...(config.collections ?? []),\n\t\tbuildFormsCollection({\n\t\t\tregistry,\n\t\t\truleRegistry,\n\t\t\tcalcAllowed,\n\t\t\tcalcSources,\n\t\t\tconsentSources,\n\t\t\tconsentResolveOnRead,\n\t\t\tactionRegistry,\n\t\t\tlocalizeContent,\n\t\t\trichText,\n\t\t\tuploadsCollectionSlug: uploadSlug,\n\t\t\tresultsAccess,\n\t\t\tpollVotesEnabled,\n\t\t\tpollSourceRegistry,\n\t\t\tpollTypeRegistry,\n\t\t\toutcomeFields,\n\t\t\tbuttons,\n\t\t\tsettings,\n\t\t\tresponse,\n\t\t\tfromAddresses,\n\t\t\tfromSources,\n\t\t\tdepartments,\n\t\t\tredirectRelationships,\n\t\t\tslugs,\n\t\t\toverrides: overrides?.forms,\n\t\t}),\n\t\tbuildSubmissionsCollection({\n\t\t\tregistry,\n\t\t\truleRegistry,\n\t\t\tcalcSources,\n\t\t\tcalcFunctions,\n\t\t\tconsentSources,\n\t\t\tconsentSnapshot,\n\t\t\tactionRegistry,\n\t\t\trichText,\n\t\t\tevents,\n\t\t\thasRunner,\n\t\t\tdispatchDeadlineMs,\n\t\t\tuploadSlug,\n\t\t\tspam,\n\t\t\tvotedCookie,\n\t\t\tpollSourceRegistry,\n\t\t\tpollVotes,\n\t\t\tshowRawFields: showSubmissionRawFields,\n\t\t\tslugs,\n\t\t\toverrides: overrides?.formSubmissions,\n\t\t}),\n\t\t...(pollVotes === false\n\t\t\t? []\n\t\t\t: [buildPollVotesCollection({ overrides: pollVotes.overrides, slug: slugs.pollVotes })]),\n\t]\n}\n"],"mappings":";;;;;;;AA2EA,MAAa,uBAAuB,EACnC,QACA,UACA,cACA,aACA,aACA,eACA,gBACA,iBACA,sBACA,gBACA,UACA,eACA,oBACA,QACA,SACA,MACA,yBACA,iBACA,eACA,aACA,oBACA,kBACA,eACA,WACA,SACA,UACA,UACA,eACA,aACA,aACA,uBACA,OACA,gBACoC;CACpC,MAAM,mBAAmB,cAAc;CACvC,oBAAoB,QAAQ,gBAAgB,QAAQ;CACpD,sBAAsB,QAAQ,gBAAgB;CAC9C,MAAM,YAAY,QAAQ,OAAO,MAAM,OAAO,KAAK;CAEnD,MAAM,aAAa,YAAY,QAAQ,KAAA,IAAY,QAAQ;CAC3D,IAAI,YAAY,OACf,wBAAwB;EAAE;EAAQ,MAAM,QAAQ;EAAY;CAAK,CAAC;CAMnE,OAAO,cAAc;EACpB,GAAI,OAAO,eAAe,CAAC;EAC3B,qBAAqB;GACpB;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA,uBAAuB;GACvB;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA,WAAW,WAAW;EACvB,CAAC;EACD,2BAA2B;GAC1B;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA,eAAe;GACf;GACA,WAAW,WAAW;EACvB,CAAC;EACD,GAAI,cAAc,QACf,CAAC,IACD,CAAC,yBAAyB;GAAE,WAAW,UAAU;GAAW,MAAM,MAAM;EAAU,CAAC,CAAC;CACxF;AACD"}
@@ -1,6 +1,6 @@
1
+ import { pluginSlugsOf } from "../plugin/collectionSlugs.js";
1
2
  import { isPollClosed, pollConfigOf } from "../form/pollState.js";
2
3
  import { resolvePollOutcome } from "./resolvePollOutcome.js";
3
- import { FORMS_SLUG } from "../collections/forms.js";
4
4
  //#region src/poll/closeJob.ts
5
5
  const POLL_CLOSE_TASK_SLUG = "form-builder-poll-close";
6
6
  /** Payload's jobs collection; queried directly to supersede a superseded pending close job. */
@@ -27,7 +27,7 @@ const shouldAutoResolvePoll = (form) => {
27
27
  const runPollClose = async (args) => {
28
28
  const { payload, input, pollVotesEnabled, req } = args;
29
29
  const form = await payload.findByID({
30
- collection: FORMS_SLUG,
30
+ collection: pluginSlugsOf(payload).forms,
31
31
  id: input.formId,
32
32
  depth: 0,
33
33
  overrideAccess: true,
@@ -1 +1 @@
1
- {"version":3,"file":"closeJob.js","names":[],"sources":["../../src/poll/closeJob.ts"],"sourcesContent":["import type { Config, Payload, PayloadRequest, TaskConfig } from 'payload'\nimport { FORMS_SLUG } from '../collections/forms'\nimport { isPollClosed, type PollConfigLike, pollConfigOf } from '../form/pollState'\nimport { resolvePollOutcome } from './resolvePollOutcome'\n\nexport const POLL_CLOSE_TASK_SLUG = 'form-builder-poll-close'\n\n/** Payload's jobs collection; queried directly to supersede a superseded pending close job. */\nconst JOBS_SLUG = 'payload-jobs'\n\n/** Input the enqueue path stores; the handler re-loads everything else from the DB. */\nexport type PollCloseTaskInput = { formId: number | string }\n\n/** The slice of a loaded form the close gate reads. */\nexport type PollFormLike = { pollEnabled?: boolean | null; poll?: unknown }\n\nconst hasEmptyWinners = (poll: PollConfigLike): boolean => {\n\tconst winners = poll.outcome?.winningValues\n\treturn (\n\t\t!Array.isArray(winners) ||\n\t\twinners.filter((value) => typeof value === 'string' && value.length > 0).length === 0\n\t)\n}\n\nconst strategyOf = (poll: PollConfigLike): string =>\n\ttypeof poll.type === 'string' && poll.type.length > 0 ? poll.type : 'manual'\n\n/**\n * True when a poll's outcome should auto-resolve now: enabled, closed, still unresolved, and its\n * strategy is not `manual`. Shared by the close-job handler and the results-read fallback so the two\n * triggers apply the exact same gate.\n */\nexport const shouldAutoResolvePoll = (form: PollFormLike): boolean => {\n\tconst poll = pollConfigOf(form.poll)\n\treturn (\n\t\tform.pollEnabled === true &&\n\t\tpoll != null &&\n\t\tisPollClosed(poll) &&\n\t\thasEmptyWinners(poll) &&\n\t\tstrategyOf(poll) !== 'manual'\n\t)\n}\n\n/**\n * Run the scheduled close for one form. Idempotent: re-loads the form and only resolves when the\n * poll is enabled, closed, still unresolved, and not `manual`, so a re-run after the outcome was\n * recorded (or a job that fires slightly early) is a safe no-op. Tolerates a deleted form.\n */\nexport const runPollClose = async (args: {\n\tpayload: Payload\n\tinput: PollCloseTaskInput\n\t/** Whether the hidden tally store backs the outcome aggregation (see `resolvePollOutcome`). */\n\tpollVotesEnabled?: boolean\n\treq?: PayloadRequest\n}): Promise<void> => {\n\tconst { payload, input, pollVotesEnabled, req } = args\n\tconst form = await payload\n\t\t.findByID({ collection: FORMS_SLUG, id: input.formId, depth: 0, overrideAccess: true, req })\n\t\t.catch(() => null)\n\tif (!form || !shouldAutoResolvePoll(form as PollFormLike)) {\n\t\treturn\n\t}\n\tawait resolvePollOutcome({ payload, formId: input.formId, pollVotesEnabled, req })\n}\n\n/** Native Payload jobs task that resolves a poll's outcome once it closes. */\nexport const buildPollCloseTask = (pollVotesEnabled = false): TaskConfig =>\n\t({\n\t\tslug: POLL_CLOSE_TASK_SLUG,\n\t\tinputSchema: [{ name: 'formId', type: 'text', required: true }],\n\t\thandler: async ({ input, req }) => {\n\t\t\tawait runPollClose({\n\t\t\t\tpayload: req.payload,\n\t\t\t\tinput: input as PollCloseTaskInput,\n\t\t\t\tpollVotesEnabled,\n\t\t\t\treq,\n\t\t\t})\n\t\t\treturn { output: {} }\n\t\t},\n\t}) as TaskConfig\n\n/** Register the poll-close task on `config.jobs.tasks`, creating the jobs config if absent. */\nexport const registerPollCloseTask = (config: Config, pollVotesEnabled = false): void => {\n\tconfig.jobs ??= {}\n\tconfig.jobs.tasks ??= []\n\tconfig.jobs.tasks.push(buildPollCloseTask(pollVotesEnabled))\n}\n\nconst canQueue = (payload: Payload): boolean => typeof payload.jobs?.queue === 'function'\n\n/**\n * Delete this form's pending (never-run) close jobs so a re-enqueue supersedes the prior one. Payload\n * 3.85's `jobs.queue` has no concurrency key, so this mirrors core's `deleteScheduledPublishJobs`:\n * a direct `db.deleteMany` on the jobs collection, scoped by task slug and the JSON `input.formId`.\n * Best-effort; a failure here only risks a duplicate future job, which the idempotent handler absorbs.\n */\nconst deletePendingCloseJobs = async (\n\tpayload: Payload,\n\tformId: number | string,\n\treq?: PayloadRequest\n): Promise<void> => {\n\ttry {\n\t\tawait payload.db.deleteMany({\n\t\t\tcollection: JOBS_SLUG,\n\t\t\treq,\n\t\t\twhere: {\n\t\t\t\tand: [\n\t\t\t\t\t{ completedAt: { exists: false } },\n\t\t\t\t\t{ processing: { equals: false } },\n\t\t\t\t\t{ taskSlug: { equals: POLL_CLOSE_TASK_SLUG } },\n\t\t\t\t\t{ 'input.formId': { equals: String(formId) } },\n\t\t\t\t],\n\t\t\t},\n\t\t})\n\t} catch (error) {\n\t\tpayload.logger?.error(\n\t\t\t`@10x-media/form-builder: failed to clear pending poll-close jobs for form ${String(formId)}: ${\n\t\t\t\terror instanceof Error ? error.message : String(error)\n\t\t\t}`\n\t\t)\n\t}\n}\n\n/**\n * Schedule (or reschedule) the auto-close for a poll after a save. Enqueues a `waitUntil: closesAt`\n * job when the poll is enabled, has a `closesAt`, is still unresolved, and its strategy is not\n * `manual`, and only when a job runner is present (`payload.jobs.queue`); otherwise the resolve-on-read\n * fallback is the safety net. Any prior pending close job for the form is deleted first so the newest\n * `closesAt` wins. Never throws; a queue failure is logged and swallowed.\n */\nexport const enqueuePollClose = async (args: {\n\tpayload: Payload\n\tform: { id: number | string; pollEnabled?: boolean | null; poll?: unknown }\n\treq?: PayloadRequest\n}): Promise<void> => {\n\tconst { payload, form, req } = args\n\tconst poll = pollConfigOf(form.poll)\n\tif (form.pollEnabled !== true || !poll) {\n\t\treturn\n\t}\n\tconst closesAt =\n\t\ttypeof poll.closesAt === 'string' && poll.closesAt.length > 0 ? poll.closesAt : undefined\n\tif (!closesAt || !hasEmptyWinners(poll) || strategyOf(poll) === 'manual' || !canQueue(payload)) {\n\t\treturn\n\t}\n\tconst waitUntil = new Date(closesAt)\n\tif (Number.isNaN(waitUntil.getTime())) {\n\t\treturn\n\t}\n\tawait deletePendingCloseJobs(payload, form.id, req)\n\ttry {\n\t\tawait payload.jobs.queue({\n\t\t\ttask: POLL_CLOSE_TASK_SLUG,\n\t\t\tinput: { formId: String(form.id) },\n\t\t\twaitUntil,\n\t\t\treq,\n\t\t})\n\t} catch (error) {\n\t\tpayload.logger?.error(\n\t\t\t`@10x-media/form-builder: failed to enqueue poll-close for form ${String(form.id)}: ${\n\t\t\t\terror instanceof Error ? error.message : String(error)\n\t\t\t}`\n\t\t)\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"closeJob.js","names":[],"sources":["../../src/poll/closeJob.ts"],"sourcesContent":["import type { Config, Payload, PayloadRequest, TaskConfig } from 'payload'\nimport { isPollClosed, type PollConfigLike, pollConfigOf } from '../form/pollState'\nimport { pluginSlugsOf } from '../plugin/collectionSlugs'\nimport { resolvePollOutcome } from './resolvePollOutcome'\n\nexport const POLL_CLOSE_TASK_SLUG = 'form-builder-poll-close'\n\n/** Payload's jobs collection; queried directly to supersede a superseded pending close job. */\nconst JOBS_SLUG = 'payload-jobs'\n\n/** Input the enqueue path stores; the handler re-loads everything else from the DB. */\nexport type PollCloseTaskInput = { formId: number | string }\n\n/** The slice of a loaded form the close gate reads. */\nexport type PollFormLike = { pollEnabled?: boolean | null; poll?: unknown }\n\nconst hasEmptyWinners = (poll: PollConfigLike): boolean => {\n\tconst winners = poll.outcome?.winningValues\n\treturn (\n\t\t!Array.isArray(winners) ||\n\t\twinners.filter((value) => typeof value === 'string' && value.length > 0).length === 0\n\t)\n}\n\nconst strategyOf = (poll: PollConfigLike): string =>\n\ttypeof poll.type === 'string' && poll.type.length > 0 ? poll.type : 'manual'\n\n/**\n * True when a poll's outcome should auto-resolve now: enabled, closed, still unresolved, and its\n * strategy is not `manual`. Shared by the close-job handler and the results-read fallback so the two\n * triggers apply the exact same gate.\n */\nexport const shouldAutoResolvePoll = (form: PollFormLike): boolean => {\n\tconst poll = pollConfigOf(form.poll)\n\treturn (\n\t\tform.pollEnabled === true &&\n\t\tpoll != null &&\n\t\tisPollClosed(poll) &&\n\t\thasEmptyWinners(poll) &&\n\t\tstrategyOf(poll) !== 'manual'\n\t)\n}\n\n/**\n * Run the scheduled close for one form. Idempotent: re-loads the form and only resolves when the\n * poll is enabled, closed, still unresolved, and not `manual`, so a re-run after the outcome was\n * recorded (or a job that fires slightly early) is a safe no-op. Tolerates a deleted form.\n */\nexport const runPollClose = async (args: {\n\tpayload: Payload\n\tinput: PollCloseTaskInput\n\t/** Whether the hidden tally store backs the outcome aggregation (see `resolvePollOutcome`). */\n\tpollVotesEnabled?: boolean\n\treq?: PayloadRequest\n}): Promise<void> => {\n\tconst { payload, input, pollVotesEnabled, req } = args\n\tconst form = await payload\n\t\t.findByID({\n\t\t\tcollection: pluginSlugsOf(payload).forms,\n\t\t\tid: input.formId,\n\t\t\tdepth: 0,\n\t\t\toverrideAccess: true,\n\t\t\treq,\n\t\t})\n\t\t.catch(() => null)\n\tif (!form || !shouldAutoResolvePoll(form as PollFormLike)) {\n\t\treturn\n\t}\n\tawait resolvePollOutcome({ payload, formId: input.formId, pollVotesEnabled, req })\n}\n\n/** Native Payload jobs task that resolves a poll's outcome once it closes. */\nexport const buildPollCloseTask = (pollVotesEnabled = false): TaskConfig =>\n\t({\n\t\tslug: POLL_CLOSE_TASK_SLUG,\n\t\tinputSchema: [{ name: 'formId', type: 'text', required: true }],\n\t\thandler: async ({ input, req }) => {\n\t\t\tawait runPollClose({\n\t\t\t\tpayload: req.payload,\n\t\t\t\tinput: input as PollCloseTaskInput,\n\t\t\t\tpollVotesEnabled,\n\t\t\t\treq,\n\t\t\t})\n\t\t\treturn { output: {} }\n\t\t},\n\t}) as TaskConfig\n\n/** Register the poll-close task on `config.jobs.tasks`, creating the jobs config if absent. */\nexport const registerPollCloseTask = (config: Config, pollVotesEnabled = false): void => {\n\tconfig.jobs ??= {}\n\tconfig.jobs.tasks ??= []\n\tconfig.jobs.tasks.push(buildPollCloseTask(pollVotesEnabled))\n}\n\nconst canQueue = (payload: Payload): boolean => typeof payload.jobs?.queue === 'function'\n\n/**\n * Delete this form's pending (never-run) close jobs so a re-enqueue supersedes the prior one. Payload\n * 3.85's `jobs.queue` has no concurrency key, so this mirrors core's `deleteScheduledPublishJobs`:\n * a direct `db.deleteMany` on the jobs collection, scoped by task slug and the JSON `input.formId`.\n * Best-effort; a failure here only risks a duplicate future job, which the idempotent handler absorbs.\n */\nconst deletePendingCloseJobs = async (\n\tpayload: Payload,\n\tformId: number | string,\n\treq?: PayloadRequest\n): Promise<void> => {\n\ttry {\n\t\tawait payload.db.deleteMany({\n\t\t\tcollection: JOBS_SLUG,\n\t\t\treq,\n\t\t\twhere: {\n\t\t\t\tand: [\n\t\t\t\t\t{ completedAt: { exists: false } },\n\t\t\t\t\t{ processing: { equals: false } },\n\t\t\t\t\t{ taskSlug: { equals: POLL_CLOSE_TASK_SLUG } },\n\t\t\t\t\t{ 'input.formId': { equals: String(formId) } },\n\t\t\t\t],\n\t\t\t},\n\t\t})\n\t} catch (error) {\n\t\tpayload.logger?.error(\n\t\t\t`@10x-media/form-builder: failed to clear pending poll-close jobs for form ${String(formId)}: ${\n\t\t\t\terror instanceof Error ? error.message : String(error)\n\t\t\t}`\n\t\t)\n\t}\n}\n\n/**\n * Schedule (or reschedule) the auto-close for a poll after a save. Enqueues a `waitUntil: closesAt`\n * job when the poll is enabled, has a `closesAt`, is still unresolved, and its strategy is not\n * `manual`, and only when a job runner is present (`payload.jobs.queue`); otherwise the resolve-on-read\n * fallback is the safety net. Any prior pending close job for the form is deleted first so the newest\n * `closesAt` wins. Never throws; a queue failure is logged and swallowed.\n */\nexport const enqueuePollClose = async (args: {\n\tpayload: Payload\n\tform: { id: number | string; pollEnabled?: boolean | null; poll?: unknown }\n\treq?: PayloadRequest\n}): Promise<void> => {\n\tconst { payload, form, req } = args\n\tconst poll = pollConfigOf(form.poll)\n\tif (form.pollEnabled !== true || !poll) {\n\t\treturn\n\t}\n\tconst closesAt =\n\t\ttypeof poll.closesAt === 'string' && poll.closesAt.length > 0 ? poll.closesAt : undefined\n\tif (!closesAt || !hasEmptyWinners(poll) || strategyOf(poll) === 'manual' || !canQueue(payload)) {\n\t\treturn\n\t}\n\tconst waitUntil = new Date(closesAt)\n\tif (Number.isNaN(waitUntil.getTime())) {\n\t\treturn\n\t}\n\tawait deletePendingCloseJobs(payload, form.id, req)\n\ttry {\n\t\tawait payload.jobs.queue({\n\t\t\ttask: POLL_CLOSE_TASK_SLUG,\n\t\t\tinput: { formId: String(form.id) },\n\t\t\twaitUntil,\n\t\t\treq,\n\t\t})\n\t} catch (error) {\n\t\tpayload.logger?.error(\n\t\t\t`@10x-media/form-builder: failed to enqueue poll-close for form ${String(form.id)}: ${\n\t\t\t\terror instanceof Error ? error.message : String(error)\n\t\t\t}`\n\t\t)\n\t}\n}\n"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  import { keys } from "../translations/keys.js";
2
2
  import { asTranslate } from "../translations/server.js";
3
+ import { pluginSlugsOf } from "../plugin/collectionSlugs.js";
3
4
  import { pollConfigOf } from "../form/pollState.js";
4
5
  import { resolveEffectivePollOptions } from "./effectivePollOptions.js";
5
- import { FORMS_SLUG } from "../collections/forms.js";
6
6
  import { ValidationError } from "payload";
7
7
  //#region src/poll/outcomeBeforeChange.ts
8
8
  const WINNING_VALUES_PATH = "poll.outcome.winningValues";
@@ -46,7 +46,7 @@ const pollOutcomeBeforeChange = async ({ data, originalDoc, req }) => {
46
46
  const t = asTranslate(req.t);
47
47
  const fail = (message) => {
48
48
  throw new ValidationError({
49
- collection: FORMS_SLUG,
49
+ collection: pluginSlugsOf(req.payload).forms,
50
50
  errors: [{
51
51
  path: WINNING_VALUES_PATH,
52
52
  message
@@ -1 +1 @@
1
- {"version":3,"file":"outcomeBeforeChange.js","names":[],"sources":["../../src/poll/outcomeBeforeChange.ts"],"sourcesContent":["import { type CollectionBeforeChangeHook, ValidationError } from 'payload'\nimport { FORMS_SLUG } from '../collections/forms'\nimport { pollConfigOf } from '../form/pollState'\nimport { keys } from '../translations/keys'\nimport { asTranslate } from '../translations/server'\nimport type { PollOption } from './definePollOptionSource'\nimport { resolveEffectivePollOptions } from './effectivePollOptions'\n\nconst WINNING_VALUES_PATH = 'poll.outcome.winningValues'\n\n/** The winning set as a deduped array of non-empty strings, ready to store and to compare. */\nconst normalizeWinning = (value: unknown): string[] => {\n\tif (!Array.isArray(value)) {\n\t\treturn []\n\t}\n\tconst seen = new Set<string>()\n\tfor (const entry of value) {\n\t\tif (typeof entry === 'string' && entry.length > 0) {\n\t\t\tseen.add(entry)\n\t\t}\n\t}\n\treturn [...seen]\n}\n\n/** Set equality on two already-deduped winner arrays: order-insensitive membership. */\nconst sameWinners = (a: string[], b: string[]): boolean => {\n\tif (a.length !== b.length) {\n\t\treturn false\n\t}\n\tconst set = new Set(a)\n\treturn b.every((value) => set.has(value))\n}\n\n/**\n * Owns the poll outcome lifecycle for every create and update, admin and API alike. When the save\n * writes `poll.outcome.winningValues` it validates a set or changed winner set against the poll's\n * effective options (source-resolved or authored, over a merged view of the doc so partial updates\n * validate against stored config) and requires the poll to be enabled; clearing is always allowed.\n * A tie is a set with more than one winner; every value must be a member of the effective options.\n *\n * It is also the single `resolvedAt` stamping point: stamped when the winner set is set or changed,\n * cleared with the set, untouched when the set is unchanged (order-insensitive). Callers can never\n * write `resolvedAt` themselves (field-level access strips it during the beforeValidate traversal,\n * before this hook runs), while values this hook sets survive to the database, so the stamp always\n * reflects the actual transition regardless of the write path.\n */\nexport const pollOutcomeBeforeChange: CollectionBeforeChangeHook = async ({\n\tdata,\n\toriginalDoc,\n\treq,\n}) => {\n\tconst incomingPoll =\n\t\tdata?.poll != null && typeof data.poll === 'object'\n\t\t\t? (data.poll as Record<string, unknown>)\n\t\t\t: undefined\n\tconst outcome =\n\t\tincomingPoll?.outcome != null && typeof incomingPoll.outcome === 'object'\n\t\t\t? (incomingPoll.outcome as Record<string, unknown>)\n\t\t\t: undefined\n\tif (!outcome || !('winningValues' in outcome)) {\n\t\treturn data\n\t}\n\tconst next = normalizeWinning(outcome.winningValues)\n\toutcome.winningValues = next\n\tconst previous = normalizeWinning(pollConfigOf(originalDoc?.poll)?.outcome?.winningValues)\n\tif (sameWinners(next, previous)) {\n\t\treturn data\n\t}\n\tif (next.length === 0) {\n\t\toutcome.resolvedAt = null\n\t\treturn data\n\t}\n\n\tconst t = asTranslate(req.t)\n\tconst fail = (message: string): never => {\n\t\tthrow new ValidationError(\n\t\t\t{ collection: FORMS_SLUG, errors: [{ path: WINNING_VALUES_PATH, message }] },\n\t\t\treq.t\n\t\t)\n\t}\n\tconst originalPoll =\n\t\toriginalDoc?.poll != null && typeof originalDoc.poll === 'object'\n\t\t\t? (originalDoc.poll as Record<string, unknown>)\n\t\t\t: undefined\n\tconst mergedPoll = { ...(originalPoll ?? {}), ...incomingPoll }\n\t// The poll-enable flag lives at the document root now; merge the incoming partial over the stored\n\t// doc so a partial update that omits it still validates against the persisted value.\n\tconst mergedPollEnabled = { ...(originalDoc ?? {}), ...data }.pollEnabled === true\n\tif (!mergedPollEnabled) {\n\t\tfail(t(keys.validationWinningValueDisabled))\n\t}\n\tconst id = (originalDoc?.id ?? data?.id) as number | string | undefined\n\tconst form = {\n\t\tid: id ?? '',\n\t\ttitle:\n\t\t\ttypeof data?.title === 'string' ? data.title : (originalDoc?.title as string | undefined),\n\t\tpollEnabled: mergedPollEnabled,\n\t\tpoll: mergedPoll,\n\t\tfields: data?.fields ?? originalDoc?.fields,\n\t}\n\tlet options: PollOption[]\n\ttry {\n\t\t// Without a doc id (create) the per-request options cache would key on '', so skip it.\n\t\toptions = await resolveEffectivePollOptions({\n\t\t\tpayload: req.payload,\n\t\t\treq: id != null ? req : undefined,\n\t\t\tform,\n\t\t})\n\t} catch {\n\t\treturn fail(t(keys.pollOptionsUnavailable))\n\t}\n\tconst allowed = new Set(options.map((option) => option.value))\n\tif (!next.every((value) => allowed.has(value))) {\n\t\tfail(t(keys.validationWinningValueUnknown))\n\t}\n\toutcome.resolvedAt = new Date().toISOString()\n\treturn data\n}\n"],"mappings":";;;;;;;AAQA,MAAM,sBAAsB;;AAG5B,MAAM,oBAAoB,UAA6B;CACtD,IAAI,CAAC,MAAM,QAAQ,KAAK,GACvB,OAAO,CAAC;CAET,MAAM,uBAAO,IAAI,IAAY;CAC7B,KAAK,MAAM,SAAS,OACnB,IAAI,OAAO,UAAU,YAAY,MAAM,SAAS,GAC/C,KAAK,IAAI,KAAK;CAGhB,OAAO,CAAC,GAAG,IAAI;AAChB;;AAGA,MAAM,eAAe,GAAa,MAAyB;CAC1D,IAAI,EAAE,WAAW,EAAE,QAClB,OAAO;CAER,MAAM,MAAM,IAAI,IAAI,CAAC;CACrB,OAAO,EAAE,OAAO,UAAU,IAAI,IAAI,KAAK,CAAC;AACzC;;;;;;;;;;;;;;AAeA,MAAa,0BAAsD,OAAO,EACzE,MACA,aACA,UACK;CACL,MAAM,eACL,MAAM,QAAQ,QAAQ,OAAO,KAAK,SAAS,WACvC,KAAK,OACN,KAAA;CACJ,MAAM,UACL,cAAc,WAAW,QAAQ,OAAO,aAAa,YAAY,WAC7D,aAAa,UACd,KAAA;CACJ,IAAI,CAAC,WAAW,EAAE,mBAAmB,UACpC,OAAO;CAER,MAAM,OAAO,iBAAiB,QAAQ,aAAa;CACnD,QAAQ,gBAAgB;CAExB,IAAI,YAAY,MADC,iBAAiB,aAAa,aAAa,IAAI,GAAG,SAAS,aAC/C,CAAC,GAC7B,OAAO;CAER,IAAI,KAAK,WAAW,GAAG;EACtB,QAAQ,aAAa;EACrB,OAAO;CACR;CAEA,MAAM,IAAI,YAAY,IAAI,CAAC;CAC3B,MAAM,QAAQ,YAA2B;EACxC,MAAM,IAAI,gBACT;GAAE,YAAY;GAAY,QAAQ,CAAC;IAAE,MAAM;IAAqB;GAAQ,CAAC;EAAE,GAC3E,IAAI,CACL;CACD;CAKA,MAAM,aAAa;EAAE,IAHpB,aAAa,QAAQ,QAAQ,OAAO,YAAY,SAAS,WACrD,YAAY,OACb,KAAA,MACqC,CAAC;EAAI,GAAG;CAAa;CAG9D,MAAM,oBAAoB;EAAE,GAAI,eAAe,CAAC;EAAI,GAAG;CAAK,EAAE,gBAAgB;CAC9E,IAAI,CAAC,mBACJ,KAAK,EAAE,KAAK,8BAA8B,CAAC;CAE5C,MAAM,KAAM,aAAa,MAAM,MAAM;CACrC,MAAM,OAAO;EACZ,IAAI,MAAM;EACV,OACC,OAAO,MAAM,UAAU,WAAW,KAAK,QAAS,aAAa;EAC9D,aAAa;EACb,MAAM;EACN,QAAQ,MAAM,UAAU,aAAa;CACtC;CACA,IAAI;CACJ,IAAI;EAEH,UAAU,MAAM,4BAA4B;GAC3C,SAAS,IAAI;GACb,KAAK,MAAM,OAAO,MAAM,KAAA;GACxB;EACD,CAAC;CACF,QAAQ;EACP,OAAO,KAAK,EAAE,KAAK,sBAAsB,CAAC;CAC3C;CACA,MAAM,UAAU,IAAI,IAAI,QAAQ,KAAK,WAAW,OAAO,KAAK,CAAC;CAC7D,IAAI,CAAC,KAAK,OAAO,UAAU,QAAQ,IAAI,KAAK,CAAC,GAC5C,KAAK,EAAE,KAAK,6BAA6B,CAAC;CAE3C,QAAQ,8BAAa,IAAI,KAAK,GAAE,YAAY;CAC5C,OAAO;AACR"}
1
+ {"version":3,"file":"outcomeBeforeChange.js","names":[],"sources":["../../src/poll/outcomeBeforeChange.ts"],"sourcesContent":["import { type CollectionBeforeChangeHook, ValidationError } from 'payload'\nimport { pollConfigOf } from '../form/pollState'\nimport { pluginSlugsOf } from '../plugin/collectionSlugs'\nimport { keys } from '../translations/keys'\nimport { asTranslate } from '../translations/server'\nimport type { PollOption } from './definePollOptionSource'\nimport { resolveEffectivePollOptions } from './effectivePollOptions'\n\nconst WINNING_VALUES_PATH = 'poll.outcome.winningValues'\n\n/** The winning set as a deduped array of non-empty strings, ready to store and to compare. */\nconst normalizeWinning = (value: unknown): string[] => {\n\tif (!Array.isArray(value)) {\n\t\treturn []\n\t}\n\tconst seen = new Set<string>()\n\tfor (const entry of value) {\n\t\tif (typeof entry === 'string' && entry.length > 0) {\n\t\t\tseen.add(entry)\n\t\t}\n\t}\n\treturn [...seen]\n}\n\n/** Set equality on two already-deduped winner arrays: order-insensitive membership. */\nconst sameWinners = (a: string[], b: string[]): boolean => {\n\tif (a.length !== b.length) {\n\t\treturn false\n\t}\n\tconst set = new Set(a)\n\treturn b.every((value) => set.has(value))\n}\n\n/**\n * Owns the poll outcome lifecycle for every create and update, admin and API alike. When the save\n * writes `poll.outcome.winningValues` it validates a set or changed winner set against the poll's\n * effective options (source-resolved or authored, over a merged view of the doc so partial updates\n * validate against stored config) and requires the poll to be enabled; clearing is always allowed.\n * A tie is a set with more than one winner; every value must be a member of the effective options.\n *\n * It is also the single `resolvedAt` stamping point: stamped when the winner set is set or changed,\n * cleared with the set, untouched when the set is unchanged (order-insensitive). Callers can never\n * write `resolvedAt` themselves (field-level access strips it during the beforeValidate traversal,\n * before this hook runs), while values this hook sets survive to the database, so the stamp always\n * reflects the actual transition regardless of the write path.\n */\nexport const pollOutcomeBeforeChange: CollectionBeforeChangeHook = async ({\n\tdata,\n\toriginalDoc,\n\treq,\n}) => {\n\tconst incomingPoll =\n\t\tdata?.poll != null && typeof data.poll === 'object'\n\t\t\t? (data.poll as Record<string, unknown>)\n\t\t\t: undefined\n\tconst outcome =\n\t\tincomingPoll?.outcome != null && typeof incomingPoll.outcome === 'object'\n\t\t\t? (incomingPoll.outcome as Record<string, unknown>)\n\t\t\t: undefined\n\tif (!outcome || !('winningValues' in outcome)) {\n\t\treturn data\n\t}\n\tconst next = normalizeWinning(outcome.winningValues)\n\toutcome.winningValues = next\n\tconst previous = normalizeWinning(pollConfigOf(originalDoc?.poll)?.outcome?.winningValues)\n\tif (sameWinners(next, previous)) {\n\t\treturn data\n\t}\n\tif (next.length === 0) {\n\t\toutcome.resolvedAt = null\n\t\treturn data\n\t}\n\n\tconst t = asTranslate(req.t)\n\tconst fail = (message: string): never => {\n\t\tthrow new ValidationError(\n\t\t\t{\n\t\t\t\tcollection: pluginSlugsOf(req.payload).forms,\n\t\t\t\terrors: [{ path: WINNING_VALUES_PATH, message }],\n\t\t\t},\n\t\t\treq.t\n\t\t)\n\t}\n\tconst originalPoll =\n\t\toriginalDoc?.poll != null && typeof originalDoc.poll === 'object'\n\t\t\t? (originalDoc.poll as Record<string, unknown>)\n\t\t\t: undefined\n\tconst mergedPoll = { ...(originalPoll ?? {}), ...incomingPoll }\n\t// The poll-enable flag lives at the document root now; merge the incoming partial over the stored\n\t// doc so a partial update that omits it still validates against the persisted value.\n\tconst mergedPollEnabled = { ...(originalDoc ?? {}), ...data }.pollEnabled === true\n\tif (!mergedPollEnabled) {\n\t\tfail(t(keys.validationWinningValueDisabled))\n\t}\n\tconst id = (originalDoc?.id ?? data?.id) as number | string | undefined\n\tconst form = {\n\t\tid: id ?? '',\n\t\ttitle:\n\t\t\ttypeof data?.title === 'string' ? data.title : (originalDoc?.title as string | undefined),\n\t\tpollEnabled: mergedPollEnabled,\n\t\tpoll: mergedPoll,\n\t\tfields: data?.fields ?? originalDoc?.fields,\n\t}\n\tlet options: PollOption[]\n\ttry {\n\t\t// Without a doc id (create) the per-request options cache would key on '', so skip it.\n\t\toptions = await resolveEffectivePollOptions({\n\t\t\tpayload: req.payload,\n\t\t\treq: id != null ? req : undefined,\n\t\t\tform,\n\t\t})\n\t} catch {\n\t\treturn fail(t(keys.pollOptionsUnavailable))\n\t}\n\tconst allowed = new Set(options.map((option) => option.value))\n\tif (!next.every((value) => allowed.has(value))) {\n\t\tfail(t(keys.validationWinningValueUnknown))\n\t}\n\toutcome.resolvedAt = new Date().toISOString()\n\treturn data\n}\n"],"mappings":";;;;;;;AAQA,MAAM,sBAAsB;;AAG5B,MAAM,oBAAoB,UAA6B;CACtD,IAAI,CAAC,MAAM,QAAQ,KAAK,GACvB,OAAO,CAAC;CAET,MAAM,uBAAO,IAAI,IAAY;CAC7B,KAAK,MAAM,SAAS,OACnB,IAAI,OAAO,UAAU,YAAY,MAAM,SAAS,GAC/C,KAAK,IAAI,KAAK;CAGhB,OAAO,CAAC,GAAG,IAAI;AAChB;;AAGA,MAAM,eAAe,GAAa,MAAyB;CAC1D,IAAI,EAAE,WAAW,EAAE,QAClB,OAAO;CAER,MAAM,MAAM,IAAI,IAAI,CAAC;CACrB,OAAO,EAAE,OAAO,UAAU,IAAI,IAAI,KAAK,CAAC;AACzC;;;;;;;;;;;;;;AAeA,MAAa,0BAAsD,OAAO,EACzE,MACA,aACA,UACK;CACL,MAAM,eACL,MAAM,QAAQ,QAAQ,OAAO,KAAK,SAAS,WACvC,KAAK,OACN,KAAA;CACJ,MAAM,UACL,cAAc,WAAW,QAAQ,OAAO,aAAa,YAAY,WAC7D,aAAa,UACd,KAAA;CACJ,IAAI,CAAC,WAAW,EAAE,mBAAmB,UACpC,OAAO;CAER,MAAM,OAAO,iBAAiB,QAAQ,aAAa;CACnD,QAAQ,gBAAgB;CAExB,IAAI,YAAY,MADC,iBAAiB,aAAa,aAAa,IAAI,GAAG,SAAS,aAC/C,CAAC,GAC7B,OAAO;CAER,IAAI,KAAK,WAAW,GAAG;EACtB,QAAQ,aAAa;EACrB,OAAO;CACR;CAEA,MAAM,IAAI,YAAY,IAAI,CAAC;CAC3B,MAAM,QAAQ,YAA2B;EACxC,MAAM,IAAI,gBACT;GACC,YAAY,cAAc,IAAI,OAAO,EAAE;GACvC,QAAQ,CAAC;IAAE,MAAM;IAAqB;GAAQ,CAAC;EAChD,GACA,IAAI,CACL;CACD;CAKA,MAAM,aAAa;EAAE,IAHpB,aAAa,QAAQ,QAAQ,OAAO,YAAY,SAAS,WACrD,YAAY,OACb,KAAA,MACqC,CAAC;EAAI,GAAG;CAAa;CAG9D,MAAM,oBAAoB;EAAE,GAAI,eAAe,CAAC;EAAI,GAAG;CAAK,EAAE,gBAAgB;CAC9E,IAAI,CAAC,mBACJ,KAAK,EAAE,KAAK,8BAA8B,CAAC;CAE5C,MAAM,KAAM,aAAa,MAAM,MAAM;CACrC,MAAM,OAAO;EACZ,IAAI,MAAM;EACV,OACC,OAAO,MAAM,UAAU,WAAW,KAAK,QAAS,aAAa;EAC9D,aAAa;EACb,MAAM;EACN,QAAQ,MAAM,UAAU,aAAa;CACtC;CACA,IAAI;CACJ,IAAI;EAEH,UAAU,MAAM,4BAA4B;GAC3C,SAAS,IAAI;GACb,KAAK,MAAM,OAAO,MAAM,KAAA;GACxB;EACD,CAAC;CACF,QAAQ;EACP,OAAO,KAAK,EAAE,KAAK,sBAAsB,CAAC;CAC3C;CACA,MAAM,UAAU,IAAI,IAAI,QAAQ,KAAK,WAAW,OAAO,KAAK,CAAC;CAC7D,IAAI,CAAC,KAAK,OAAO,UAAU,QAAQ,IAAI,KAAK,CAAC,GAC5C,KAAK,EAAE,KAAK,6BAA6B,CAAC;CAE3C,QAAQ,8BAAa,IAAI,KAAK,GAAE,YAAY;CAC5C,OAAO;AACR"}
@@ -1,8 +1,8 @@
1
1
  import { keys } from "../translations/keys.js";
2
2
  import { asTranslate } from "../translations/server.js";
3
+ import { pluginSlugsOf } from "../plugin/collectionSlugs.js";
3
4
  import { isPollClosed, pollConfigOf } from "../form/pollState.js";
4
5
  import { resolvePollOutcome } from "./resolvePollOutcome.js";
5
- import { FORMS_SLUG } from "../collections/forms.js";
6
6
  //#region src/poll/resolvePollCloseRequest.ts
7
7
  /** The recorded winning set as non-empty strings; empty means no winner has been set yet. */
8
8
  const winnersOf = (poll) => {
@@ -57,7 +57,7 @@ const resolvePollCloseRequest = async (args) => {
57
57
  body: { errors: [{ message: "Missing form id" }] }
58
58
  };
59
59
  const form = await payload.findByID({
60
- collection: FORMS_SLUG,
60
+ collection: pluginSlugsOf(payload).forms,
61
61
  id: formId,
62
62
  depth: 0,
63
63
  overrideAccess: true,
@@ -80,7 +80,7 @@ const resolvePollCloseRequest = async (args) => {
80
80
  }
81
81
  const closesAt = (/* @__PURE__ */ new Date()).toISOString();
82
82
  await payload.update({
83
- collection: FORMS_SLUG,
83
+ collection: pluginSlugsOf(payload).forms,
84
84
  id: formId,
85
85
  data: { poll: { closesAt } },
86
86
  depth: 0,
@@ -1 +1 @@
1
- {"version":3,"file":"resolvePollCloseRequest.js","names":[],"sources":["../../src/poll/resolvePollCloseRequest.ts"],"sourcesContent":["import type { Payload, PayloadRequest } from 'payload'\nimport { FORMS_SLUG } from '../collections/forms'\nimport { isPollClosed, type PollConfigLike, pollConfigOf } from '../form/pollState'\nimport { keys } from '../translations/keys'\nimport { asTranslate } from '../translations/server'\nimport { resolvePollOutcome } from './resolvePollOutcome'\n\n/** The recorded winning set as non-empty strings; empty means no winner has been set yet. */\nconst winnersOf = (poll: PollConfigLike): string[] => {\n\tconst winners = poll.outcome?.winningValues\n\tif (!Array.isArray(winners)) {\n\t\treturn []\n\t}\n\treturn winners.filter((value): value is string => typeof value === 'string' && value.length > 0)\n}\n\n/** The selected outcome strategy slug; an absent or empty `type` is `manual`. */\nconst strategyOf = (poll: PollConfigLike): string =>\n\ttypeof poll.type === 'string' && poll.type.length > 0 ? poll.type : 'manual'\n\n/** The slice of a loaded form the close planner reads. */\nexport type PollCloseFormLike = { pollEnabled?: boolean | null; poll?: unknown }\n\nexport type PollClosePlan =\n\t| { ok: false; reason: 'already-closed' | 'manual-no-winner' | 'not-poll' }\n\t| { ok: true; resolveOutcome: boolean }\n\n/**\n * Decide what closing a poll now should do, purely from a loaded form so the branch logic is testable\n * without a boot. `manual` requires a winner to already be recorded (closing only stamps the time);\n * `mostVoted`, `source`, and any host strategy auto-resolve on close. Refuses a non-poll form and an\n * already-closed poll (idempotency), so the request helper performs no write unless this returns `ok`.\n */\nexport const planPollClose = (form: PollCloseFormLike): PollClosePlan => {\n\tconst poll = pollConfigOf(form.poll)\n\tif (form.pollEnabled !== true || !poll) {\n\t\treturn { ok: false, reason: 'not-poll' }\n\t}\n\tif (isPollClosed(poll)) {\n\t\treturn { ok: false, reason: 'already-closed' }\n\t}\n\tif (strategyOf(poll) === 'manual' && winnersOf(poll).length === 0) {\n\t\treturn { ok: false, reason: 'manual-no-winner' }\n\t}\n\treturn { ok: true, resolveOutcome: strategyOf(poll) !== 'manual' }\n}\n\nexport type ResolvePollCloseRequestArgs = {\n\tpayload: Payload\n\tformId: number | string | undefined\n\t/** Whether the caller is authenticated (an admin/user). */\n\tisAuthed: boolean\n\t/** Whether the hidden tally store backs the outcome aggregation (see `resolvePollOutcome`). */\n\tpollVotesEnabled?: boolean\n\treq: PayloadRequest\n}\n\nexport type ResolvePollCloseRequestResult = {\n\tstatus: number\n\tbody: { closesAt: string; winningValues: string[] } | { errors: { message: string }[] }\n}\n\n/**\n * Authorize and perform a manual poll close backing `POST /:id/close`. Only an authenticated caller\n * may close (anonymous is always 403, mirroring the other resolve* helpers); the route is a trusted\n * admin action, so the load and write run with `overrideAccess`. Sets `closesAt` to now through\n * `payload.update` so the collection hooks run, then for a non-manual strategy resolves the winners via\n * `resolvePollOutcome` (auto mode; a non-decidable poll simply records none and the read fallback heals\n * it later). A `manual` poll with no winner recorded is refused (400) so closing never strands a poll\n * with no result. Idempotent: an already-closed poll returns 400 rather than re-closing.\n */\nexport const resolvePollCloseRequest = async (\n\targs: ResolvePollCloseRequestArgs\n): Promise<ResolvePollCloseRequestResult> => {\n\tconst { payload, formId, isAuthed, pollVotesEnabled, req } = 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\tconst form = await payload\n\t\t.findByID({ collection: FORMS_SLUG, id: formId, depth: 0, overrideAccess: true, req })\n\t\t.catch(() => null)\n\tif (!form) {\n\t\treturn { status: 404, body: { errors: [{ message: 'Not found' }] } }\n\t}\n\n\tconst plan = planPollClose(form as PollCloseFormLike)\n\tif (!plan.ok) {\n\t\tif (plan.reason === 'manual-no-winner') {\n\t\t\treturn {\n\t\t\t\tstatus: 400,\n\t\t\t\tbody: { errors: [{ message: asTranslate(req.t)(keys.pollCloseManualNoWinner) }] },\n\t\t\t}\n\t\t}\n\t\tconst message = plan.reason === 'already-closed' ? 'Poll already closed' : 'Not a poll'\n\t\treturn { status: 400, body: { errors: [{ message }] } }\n\t}\n\n\tconst closesAt = new Date().toISOString()\n\tawait payload.update({\n\t\tcollection: FORMS_SLUG,\n\t\tid: formId,\n\t\tdata: { poll: { closesAt } },\n\t\tdepth: 0,\n\t\toverrideAccess: true,\n\t\treq,\n\t})\n\n\tconst winningValues = plan.resolveOutcome\n\t\t? await resolvePollOutcome({ payload, formId, pollVotesEnabled, req })\n\t\t: winnersOf(pollConfigOf(form.poll) ?? {})\n\treturn { status: 200, body: { closesAt, winningValues } }\n}\n"],"mappings":";;;;;;;AAQA,MAAM,aAAa,SAAmC;CACrD,MAAM,UAAU,KAAK,SAAS;CAC9B,IAAI,CAAC,MAAM,QAAQ,OAAO,GACzB,OAAO,CAAC;CAET,OAAO,QAAQ,QAAQ,UAA2B,OAAO,UAAU,YAAY,MAAM,SAAS,CAAC;AAChG;;AAGA,MAAM,cAAc,SACnB,OAAO,KAAK,SAAS,YAAY,KAAK,KAAK,SAAS,IAAI,KAAK,OAAO;;;;;;;AAerE,MAAa,iBAAiB,SAA2C;CACxE,MAAM,OAAO,aAAa,KAAK,IAAI;CACnC,IAAI,KAAK,gBAAgB,QAAQ,CAAC,MACjC,OAAO;EAAE,IAAI;EAAO,QAAQ;CAAW;CAExC,IAAI,aAAa,IAAI,GACpB,OAAO;EAAE,IAAI;EAAO,QAAQ;CAAiB;CAE9C,IAAI,WAAW,IAAI,MAAM,YAAY,UAAU,IAAI,EAAE,WAAW,GAC/D,OAAO;EAAE,IAAI;EAAO,QAAQ;CAAmB;CAEhD,OAAO;EAAE,IAAI;EAAM,gBAAgB,WAAW,IAAI,MAAM;CAAS;AAClE;;;;;;;;;;AA0BA,MAAa,0BAA0B,OACtC,SAC4C;CAC5C,MAAM,EAAE,SAAS,QAAQ,UAAU,kBAAkB,QAAQ;CAC7D,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;CAE1E,MAAM,OAAO,MAAM,QACjB,SAAS;EAAE,YAAY;EAAY,IAAI;EAAQ,OAAO;EAAG,gBAAgB;EAAM;CAAI,CAAC,EACpF,YAAY,IAAI;CAClB,IAAI,CAAC,MACJ,OAAO;EAAE,QAAQ;EAAK,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,YAAY,CAAC,EAAE;CAAE;CAGpE,MAAM,OAAO,cAAc,IAAyB;CACpD,IAAI,CAAC,KAAK,IAAI;EACb,IAAI,KAAK,WAAW,oBACnB,OAAO;GACN,QAAQ;GACR,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,YAAY,IAAI,CAAC,EAAE,KAAK,uBAAuB,EAAE,CAAC,EAAE;EACjF;EAGD,OAAO;GAAE,QAAQ;GAAK,MAAM,EAAE,QAAQ,CAAC,EAAE,SADzB,KAAK,WAAW,mBAAmB,wBAAwB,aAC1B,CAAC,EAAE;EAAE;CACvD;CAEA,MAAM,4BAAW,IAAI,KAAK,GAAE,YAAY;CACxC,MAAM,QAAQ,OAAO;EACpB,YAAY;EACZ,IAAI;EACJ,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE;EAC3B,OAAO;EACP,gBAAgB;EAChB;CACD,CAAC;CAKD,OAAO;EAAE,QAAQ;EAAK,MAAM;GAAE;GAAU,eAHlB,KAAK,iBACxB,MAAM,mBAAmB;IAAE;IAAS;IAAQ;IAAkB;GAAI,CAAC,IACnE,UAAU,aAAa,KAAK,IAAI,KAAK,CAAC,CAAC;EACY;CAAE;AACzD"}
1
+ {"version":3,"file":"resolvePollCloseRequest.js","names":[],"sources":["../../src/poll/resolvePollCloseRequest.ts"],"sourcesContent":["import type { Payload, PayloadRequest } from 'payload'\nimport { isPollClosed, type PollConfigLike, pollConfigOf } from '../form/pollState'\nimport { pluginSlugsOf } from '../plugin/collectionSlugs'\nimport { keys } from '../translations/keys'\nimport { asTranslate } from '../translations/server'\nimport { resolvePollOutcome } from './resolvePollOutcome'\n\n/** The recorded winning set as non-empty strings; empty means no winner has been set yet. */\nconst winnersOf = (poll: PollConfigLike): string[] => {\n\tconst winners = poll.outcome?.winningValues\n\tif (!Array.isArray(winners)) {\n\t\treturn []\n\t}\n\treturn winners.filter((value): value is string => typeof value === 'string' && value.length > 0)\n}\n\n/** The selected outcome strategy slug; an absent or empty `type` is `manual`. */\nconst strategyOf = (poll: PollConfigLike): string =>\n\ttypeof poll.type === 'string' && poll.type.length > 0 ? poll.type : 'manual'\n\n/** The slice of a loaded form the close planner reads. */\nexport type PollCloseFormLike = { pollEnabled?: boolean | null; poll?: unknown }\n\nexport type PollClosePlan =\n\t| { ok: false; reason: 'already-closed' | 'manual-no-winner' | 'not-poll' }\n\t| { ok: true; resolveOutcome: boolean }\n\n/**\n * Decide what closing a poll now should do, purely from a loaded form so the branch logic is testable\n * without a boot. `manual` requires a winner to already be recorded (closing only stamps the time);\n * `mostVoted`, `source`, and any host strategy auto-resolve on close. Refuses a non-poll form and an\n * already-closed poll (idempotency), so the request helper performs no write unless this returns `ok`.\n */\nexport const planPollClose = (form: PollCloseFormLike): PollClosePlan => {\n\tconst poll = pollConfigOf(form.poll)\n\tif (form.pollEnabled !== true || !poll) {\n\t\treturn { ok: false, reason: 'not-poll' }\n\t}\n\tif (isPollClosed(poll)) {\n\t\treturn { ok: false, reason: 'already-closed' }\n\t}\n\tif (strategyOf(poll) === 'manual' && winnersOf(poll).length === 0) {\n\t\treturn { ok: false, reason: 'manual-no-winner' }\n\t}\n\treturn { ok: true, resolveOutcome: strategyOf(poll) !== 'manual' }\n}\n\nexport type ResolvePollCloseRequestArgs = {\n\tpayload: Payload\n\tformId: number | string | undefined\n\t/** Whether the caller is authenticated (an admin/user). */\n\tisAuthed: boolean\n\t/** Whether the hidden tally store backs the outcome aggregation (see `resolvePollOutcome`). */\n\tpollVotesEnabled?: boolean\n\treq: PayloadRequest\n}\n\nexport type ResolvePollCloseRequestResult = {\n\tstatus: number\n\tbody: { closesAt: string; winningValues: string[] } | { errors: { message: string }[] }\n}\n\n/**\n * Authorize and perform a manual poll close backing `POST /:id/close`. Only an authenticated caller\n * may close (anonymous is always 403, mirroring the other resolve* helpers); the route is a trusted\n * admin action, so the load and write run with `overrideAccess`. Sets `closesAt` to now through\n * `payload.update` so the collection hooks run, then for a non-manual strategy resolves the winners via\n * `resolvePollOutcome` (auto mode; a non-decidable poll simply records none and the read fallback heals\n * it later). A `manual` poll with no winner recorded is refused (400) so closing never strands a poll\n * with no result. Idempotent: an already-closed poll returns 400 rather than re-closing.\n */\nexport const resolvePollCloseRequest = async (\n\targs: ResolvePollCloseRequestArgs\n): Promise<ResolvePollCloseRequestResult> => {\n\tconst { payload, formId, isAuthed, pollVotesEnabled, req } = 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\tconst form = await payload\n\t\t.findByID({\n\t\t\tcollection: pluginSlugsOf(payload).forms,\n\t\t\tid: formId,\n\t\t\tdepth: 0,\n\t\t\toverrideAccess: true,\n\t\t\treq,\n\t\t})\n\t\t.catch(() => null)\n\tif (!form) {\n\t\treturn { status: 404, body: { errors: [{ message: 'Not found' }] } }\n\t}\n\n\tconst plan = planPollClose(form as PollCloseFormLike)\n\tif (!plan.ok) {\n\t\tif (plan.reason === 'manual-no-winner') {\n\t\t\treturn {\n\t\t\t\tstatus: 400,\n\t\t\t\tbody: { errors: [{ message: asTranslate(req.t)(keys.pollCloseManualNoWinner) }] },\n\t\t\t}\n\t\t}\n\t\tconst message = plan.reason === 'already-closed' ? 'Poll already closed' : 'Not a poll'\n\t\treturn { status: 400, body: { errors: [{ message }] } }\n\t}\n\n\tconst closesAt = new Date().toISOString()\n\tawait payload.update({\n\t\tcollection: pluginSlugsOf(payload).forms,\n\t\tid: formId,\n\t\tdata: { poll: { closesAt } },\n\t\tdepth: 0,\n\t\toverrideAccess: true,\n\t\treq,\n\t})\n\n\tconst winningValues = plan.resolveOutcome\n\t\t? await resolvePollOutcome({ payload, formId, pollVotesEnabled, req })\n\t\t: winnersOf(pollConfigOf(form.poll) ?? {})\n\treturn { status: 200, body: { closesAt, winningValues } }\n}\n"],"mappings":";;;;;;;AAQA,MAAM,aAAa,SAAmC;CACrD,MAAM,UAAU,KAAK,SAAS;CAC9B,IAAI,CAAC,MAAM,QAAQ,OAAO,GACzB,OAAO,CAAC;CAET,OAAO,QAAQ,QAAQ,UAA2B,OAAO,UAAU,YAAY,MAAM,SAAS,CAAC;AAChG;;AAGA,MAAM,cAAc,SACnB,OAAO,KAAK,SAAS,YAAY,KAAK,KAAK,SAAS,IAAI,KAAK,OAAO;;;;;;;AAerE,MAAa,iBAAiB,SAA2C;CACxE,MAAM,OAAO,aAAa,KAAK,IAAI;CACnC,IAAI,KAAK,gBAAgB,QAAQ,CAAC,MACjC,OAAO;EAAE,IAAI;EAAO,QAAQ;CAAW;CAExC,IAAI,aAAa,IAAI,GACpB,OAAO;EAAE,IAAI;EAAO,QAAQ;CAAiB;CAE9C,IAAI,WAAW,IAAI,MAAM,YAAY,UAAU,IAAI,EAAE,WAAW,GAC/D,OAAO;EAAE,IAAI;EAAO,QAAQ;CAAmB;CAEhD,OAAO;EAAE,IAAI;EAAM,gBAAgB,WAAW,IAAI,MAAM;CAAS;AAClE;;;;;;;;;;AA0BA,MAAa,0BAA0B,OACtC,SAC4C;CAC5C,MAAM,EAAE,SAAS,QAAQ,UAAU,kBAAkB,QAAQ;CAC7D,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;CAE1E,MAAM,OAAO,MAAM,QACjB,SAAS;EACT,YAAY,cAAc,OAAO,EAAE;EACnC,IAAI;EACJ,OAAO;EACP,gBAAgB;EAChB;CACD,CAAC,EACA,YAAY,IAAI;CAClB,IAAI,CAAC,MACJ,OAAO;EAAE,QAAQ;EAAK,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,YAAY,CAAC,EAAE;CAAE;CAGpE,MAAM,OAAO,cAAc,IAAyB;CACpD,IAAI,CAAC,KAAK,IAAI;EACb,IAAI,KAAK,WAAW,oBACnB,OAAO;GACN,QAAQ;GACR,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,YAAY,IAAI,CAAC,EAAE,KAAK,uBAAuB,EAAE,CAAC,EAAE;EACjF;EAGD,OAAO;GAAE,QAAQ;GAAK,MAAM,EAAE,QAAQ,CAAC,EAAE,SADzB,KAAK,WAAW,mBAAmB,wBAAwB,aAC1B,CAAC,EAAE;EAAE;CACvD;CAEA,MAAM,4BAAW,IAAI,KAAK,GAAE,YAAY;CACxC,MAAM,QAAQ,OAAO;EACpB,YAAY,cAAc,OAAO,EAAE;EACnC,IAAI;EACJ,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE;EAC3B,OAAO;EACP,gBAAgB;EAChB;CACD,CAAC;CAKD,OAAO;EAAE,QAAQ;EAAK,MAAM;GAAE;GAAU,eAHlB,KAAK,iBACxB,MAAM,mBAAmB;IAAE;IAAS;IAAQ;IAAkB;GAAI,CAAC,IACnE,UAAU,aAAa,KAAK,IAAI,KAAK,CAAC,CAAC;EACY;CAAE;AACzD"}
@@ -1,5 +1,5 @@
1
+ import { pluginSlugsOf } from "../plugin/collectionSlugs.js";
1
2
  import { resolveEffectivePollOptions } from "./effectivePollOptions.js";
2
- import { FORMS_SLUG } from "../collections/forms.js";
3
3
  //#region src/poll/resolvePollOptionsRequest.ts
4
4
  /**
5
5
  * Authorize and resolve the `GET /:id/poll-options` request backing the admin winner select:
@@ -19,7 +19,7 @@ const resolvePollOptionsRequest = 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":"resolvePollOptionsRequest.js","names":[],"sources":["../../src/poll/resolvePollOptionsRequest.ts"],"sourcesContent":["import type { Payload, PayloadRequest } from 'payload'\nimport { FORMS_SLUG } from '../collections/forms'\nimport type { PollOption } from './definePollOptionSource'\nimport { resolveEffectivePollOptions } from './effectivePollOptions'\n\nexport type ResolvePollOptionsRequestArgs = {\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}\n\nexport type ResolvePollOptionsRequestResult = {\n\tstatus: number\n\tbody: { options: PollOption[] } | { errors: { message: string }[] }\n}\n\n/**\n * Authorize and resolve the `GET /:id/poll-options` request backing the admin winner select:\n * authenticated callers get the poll's effective options (source-resolved or authored, exactly the\n * set outcome validation accepts), anonymous callers are always refused. Statuses mirror the\n * results endpoint: 400 missing id, 403 unauthenticated, 404 unknown form, 503 when the option\n * source fails (fail closed).\n */\nexport const resolvePollOptionsRequest = async (\n\targs: ResolvePollOptionsRequestArgs\n): Promise<ResolvePollOptionsRequestResult> => {\n\tconst { payload, formId, isAuthed, req } = 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 cannot read another tenant's\n\t// poll options.\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 options = await resolveEffectivePollOptions({ payload, req, form })\n\t\treturn { status: 200, body: { options } }\n\t} catch {\n\t\treturn { status: 503, body: { errors: [{ message: 'Poll options unavailable' }] } }\n\t}\n}\n"],"mappings":";;;;;;;;;;AAyBA,MAAa,4BAA4B,OACxC,SAC8C;CAC9C,MAAM,EAAE,SAAS,QAAQ,UAAU,QAAQ;CAC3C,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;GAAE,QAAQ;GAAK,MAAM,EAAE,SAAA,MADR,4BAA4B;IAAE;IAAS;IAAK;GAAK,CAAC,EAClC;EAAE;CACzC,QAAQ;EACP,OAAO;GAAE,QAAQ;GAAK,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,2BAA2B,CAAC,EAAE;EAAE;CACnF;AACD"}
1
+ {"version":3,"file":"resolvePollOptionsRequest.js","names":[],"sources":["../../src/poll/resolvePollOptionsRequest.ts"],"sourcesContent":["import type { Payload, PayloadRequest } from 'payload'\nimport { pluginSlugsOf } from '../plugin/collectionSlugs'\nimport type { PollOption } from './definePollOptionSource'\nimport { resolveEffectivePollOptions } from './effectivePollOptions'\n\nexport type ResolvePollOptionsRequestArgs = {\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}\n\nexport type ResolvePollOptionsRequestResult = {\n\tstatus: number\n\tbody: { options: PollOption[] } | { errors: { message: string }[] }\n}\n\n/**\n * Authorize and resolve the `GET /:id/poll-options` request backing the admin winner select:\n * authenticated callers get the poll's effective options (source-resolved or authored, exactly the\n * set outcome validation accepts), anonymous callers are always refused. Statuses mirror the\n * results endpoint: 400 missing id, 403 unauthenticated, 404 unknown form, 503 when the option\n * source fails (fail closed).\n */\nexport const resolvePollOptionsRequest = async (\n\targs: ResolvePollOptionsRequestArgs\n): Promise<ResolvePollOptionsRequestResult> => {\n\tconst { payload, formId, isAuthed, req } = 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 cannot read another tenant's\n\t// poll options.\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 options = await resolveEffectivePollOptions({ payload, req, form })\n\t\treturn { status: 200, body: { options } }\n\t} catch {\n\t\treturn { status: 503, body: { errors: [{ message: 'Poll options unavailable' }] } }\n\t}\n}\n"],"mappings":";;;;;;;;;;AAyBA,MAAa,4BAA4B,OACxC,SAC8C;CAC9C,MAAM,EAAE,SAAS,QAAQ,UAAU,QAAQ;CAC3C,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;GAAE,QAAQ;GAAK,MAAM,EAAE,SAAA,MADR,4BAA4B;IAAE;IAAS;IAAK;GAAK,CAAC,EAClC;EAAE;CACzC,QAAQ;EACP,OAAO;GAAE,QAAQ;GAAK,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,2BAA2B,CAAC,EAAE;EAAE;CACnF;AACD"}