@10x-media/form-builder 0.1.0-beta.2 → 0.1.0-beta.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +360 -0
- package/README.md +7 -5
- package/dist/actions/assertNoBlockCollision.js +18 -0
- package/dist/actions/assertNoBlockCollision.js.map +1 -0
- package/dist/actions/body/converters.d.ts +20 -0
- package/dist/actions/body/converters.js +57 -0
- package/dist/actions/body/converters.js.map +1 -0
- package/dist/actions/body/escapeHtml.d.ts +6 -0
- package/dist/actions/body/escapeHtml.js +14 -0
- package/dist/actions/body/escapeHtml.js.map +1 -0
- package/dist/actions/body/serializeBody.d.ts +55 -0
- package/dist/actions/body/serializeBody.js +78 -0
- package/dist/actions/body/serializeBody.js.map +1 -0
- package/dist/actions/body/serializeSlate.js +31 -0
- package/dist/actions/body/serializeSlate.js.map +1 -0
- package/dist/actions/body/textOfBody.d.ts +13 -0
- package/dist/actions/body/textOfBody.js +35 -0
- package/dist/actions/body/textOfBody.js.map +1 -0
- package/dist/actions/body/wildcards.d.ts +10 -0
- package/dist/actions/body/wildcards.js +33 -0
- package/dist/actions/body/wildcards.js.map +1 -0
- package/dist/actions/buildActionBlocks.js +12 -9
- package/dist/actions/buildActionBlocks.js.map +1 -1
- package/dist/actions/builtin/confirmation.js +47 -43
- package/dist/actions/builtin/confirmation.js.map +1 -1
- package/dist/actions/builtin/emailAction.d.ts +20 -0
- package/dist/actions/builtin/emailAction.js +120 -0
- package/dist/actions/builtin/emailAction.js.map +1 -0
- package/dist/actions/builtin/emailTeam.js +20 -38
- package/dist/actions/builtin/emailTeam.js.map +1 -1
- package/dist/actions/builtin/index.d.ts +16 -1
- package/dist/actions/builtin/index.js +21 -7
- package/dist/actions/builtin/index.js.map +1 -1
- package/dist/actions/builtin/signedWebhook.js +6 -2
- package/dist/actions/builtin/signedWebhook.js.map +1 -1
- package/dist/actions/defineAction.d.ts +17 -5
- package/dist/actions/defineAction.js.map +1 -1
- package/dist/actions/dispatch.js +51 -6
- package/dist/actions/dispatch.js.map +1 -1
- package/dist/actions/dispatchContext.js +13 -0
- package/dist/actions/dispatchContext.js.map +1 -0
- package/dist/actions/emailRecipients.d.ts +12 -0
- package/dist/actions/emailRecipients.js +146 -0
- package/dist/actions/emailRecipients.js.map +1 -0
- package/dist/actions/fromAddresses.d.ts +37 -0
- package/dist/actions/fromAddresses.js +131 -0
- package/dist/actions/fromAddresses.js.map +1 -0
- package/dist/actions/recipientSources.d.ts +34 -0
- package/dist/actions/recipientSources.js +11 -0
- package/dist/actions/recipientSources.js.map +1 -0
- package/dist/actions/registry.d.ts +2 -1
- package/dist/actions/registry.js +5 -7
- package/dist/actions/registry.js.map +1 -1
- package/dist/actions/runActions.js +10 -1
- package/dist/actions/runActions.js.map +1 -1
- package/dist/actions/task.js +47 -16
- package/dist/actions/task.js.map +1 -1
- package/dist/aggregation/aggregateResponses.d.ts +9 -0
- package/dist/aggregation/aggregateResponses.js +8 -14
- package/dist/aggregation/aggregateResponses.js.map +1 -1
- package/dist/aggregation/resolveResultsRequest.d.ts +41 -6
- package/dist/aggregation/resolveResultsRequest.js +97 -11
- package/dist/aggregation/resolveResultsRequest.js.map +1 -1
- package/dist/calc/computeCalcFields.d.ts +5 -3
- package/dist/calc/computeCalcFields.js +6 -4
- package/dist/calc/computeCalcFields.js.map +1 -1
- package/dist/calc/evaluate.d.ts +26 -3
- package/dist/calc/evaluate.js +34 -13
- package/dist/calc/evaluate.js.map +1 -1
- package/dist/calc/formatCalc.d.ts +11 -0
- package/dist/calc/formatCalc.js +35 -0
- package/dist/calc/formatCalc.js.map +1 -0
- package/dist/calc/formatCalcValue.d.ts +17 -0
- package/dist/calc/formatCalcValue.js +17 -0
- package/dist/calc/formatCalcValue.js.map +1 -0
- package/dist/calc/normalizeCalc.d.ts +17 -3
- package/dist/calc/normalizeCalc.js +42 -33
- package/dist/calc/normalizeCalc.js.map +1 -1
- package/dist/calc/registry.d.ts +43 -0
- package/dist/calc/registry.js +27 -0
- package/dist/calc/registry.js.map +1 -0
- package/dist/calc/resolveCalcContext.d.ts +36 -0
- package/dist/calc/resolveCalcContext.js +87 -0
- package/dist/calc/resolveCalcContext.js.map +1 -0
- package/dist/calc/types.d.ts +25 -4
- package/dist/calc/types.js +23 -0
- package/dist/calc/types.js.map +1 -0
- package/dist/client/ByteSizeField.d.ts +29 -0
- package/dist/client/ByteSizeField.js +58 -0
- package/dist/client/ByteSizeField.js.map +1 -0
- package/dist/client/CalcExpressionBuilder.d.ts +20 -0
- package/dist/client/CalcExpressionBuilder.js +890 -0
- package/dist/client/CalcExpressionBuilder.js.map +1 -0
- package/dist/client/ClosePollButton.d.ts +15 -0
- package/dist/client/ClosePollButton.js +80 -0
- package/dist/client/ClosePollButton.js.map +1 -0
- package/dist/client/CondensedArray.d.ts +15 -0
- package/dist/client/CondensedArray.js +97 -0
- package/dist/client/CondensedArray.js.map +1 -0
- package/dist/client/ConditionBuilder.js +2 -1
- package/dist/client/ConditionBuilder.js.map +1 -1
- package/dist/client/ConsentBlockLabel.d.ts +15 -0
- package/dist/client/ConsentBlockLabel.js +71 -0
- package/dist/client/ConsentBlockLabel.js.map +1 -0
- package/dist/client/ConsentSourceRowLabel.d.ts +9 -0
- package/dist/client/ConsentSourceRowLabel.js +21 -0
- package/dist/client/ConsentSourceRowLabel.js.map +1 -0
- package/dist/client/EndpointOptionsSelect.d.ts +55 -0
- package/dist/client/EndpointOptionsSelect.js +112 -0
- package/dist/client/EndpointOptionsSelect.js.map +1 -0
- package/dist/client/FieldBlockLabel.d.ts +18 -0
- package/dist/client/FieldBlockLabel.js +34 -0
- package/dist/client/FieldBlockLabel.js.map +1 -0
- package/dist/client/FieldNameSelect.d.ts +37 -0
- package/dist/client/FieldNameSelect.js +72 -0
- package/dist/client/FieldNameSelect.js.map +1 -0
- package/dist/client/FlowBuilder.d.ts +2 -2
- package/dist/client/FlowBuilder.js +137 -88
- package/dist/client/FlowBuilder.js.map +1 -1
- package/dist/client/FormConditionField.js +1 -4
- package/dist/client/FormConditionField.js.map +1 -1
- package/dist/client/RecipientsSelect.d.ts +37 -0
- package/dist/client/RecipientsSelect.js +197 -0
- package/dist/client/RecipientsSelect.js.map +1 -0
- package/dist/client/RuleDescription.d.ts +21 -0
- package/dist/client/RuleDescription.js +19 -0
- package/dist/client/RuleDescription.js.map +1 -0
- package/dist/client/admin.css +557 -0
- package/dist/client/endpointOptions.d.ts +11 -0
- package/dist/client/endpointOptions.js +38 -0
- package/dist/client/endpointOptions.js.map +1 -0
- package/dist/client/flowAuthoring.js +142 -0
- package/dist/client/flowAuthoring.js.map +1 -0
- package/dist/client/synthesizeClientField.js +21 -10
- package/dist/client/synthesizeClientField.js.map +1 -1
- package/dist/client/toStaticLabel.js +13 -0
- package/dist/client/toStaticLabel.js.map +1 -0
- package/dist/collections/buttonFields.d.ts +34 -0
- package/dist/collections/buttonFields.js +35 -0
- package/dist/collections/buttonFields.js.map +1 -0
- package/dist/collections/cells/FieldCountCell.d.ts +15 -0
- package/dist/collections/cells/FieldCountCell.js +18 -0
- package/dist/collections/cells/FieldCountCell.js.map +1 -0
- package/dist/collections/cells/FlowStepsCell.d.ts +14 -0
- package/dist/collections/cells/FlowStepsCell.js +17 -0
- package/dist/collections/cells/FlowStepsCell.js.map +1 -0
- package/dist/collections/formSubmissions.js +114 -28
- package/dist/collections/formSubmissions.js.map +1 -1
- package/dist/collections/forms.js +449 -112
- package/dist/collections/forms.js.map +1 -1
- package/dist/collections/formsEndpoints.js +106 -0
- package/dist/collections/formsEndpoints.js.map +1 -0
- package/dist/collections/redirectFields.d.ts +29 -0
- package/dist/collections/settingsFields.d.ts +31 -0
- package/dist/collections/settingsFields.js +45 -0
- package/dist/collections/settingsFields.js.map +1 -0
- package/dist/conditions/conditionType.js +9 -2
- package/dist/conditions/conditionType.js.map +1 -1
- package/dist/conditions/evaluate.js +1 -1
- package/dist/conditions/evaluate.js.map +1 -1
- package/dist/conditions/fieldTypes.d.ts +3 -1
- package/dist/conditions/fieldTypes.js +4 -1
- package/dist/conditions/fieldTypes.js.map +1 -1
- package/dist/conditions/normalizeConditions.js +20 -6
- package/dist/conditions/normalizeConditions.js.map +1 -1
- package/dist/consent/applyConsentStatements.d.ts +14 -0
- package/dist/consent/applyConsentStatements.js +17 -0
- package/dist/consent/applyConsentStatements.js.map +1 -0
- package/dist/consent/captureConsent.d.ts +39 -11
- package/dist/consent/captureConsent.js +63 -13
- package/dist/consent/captureConsent.js.map +1 -1
- package/dist/consent/consentSourcesField.d.ts +66 -0
- package/dist/consent/consentSourcesField.js +98 -0
- package/dist/consent/consentSourcesField.js.map +1 -0
- package/dist/consent/effectiveStatement.js +16 -0
- package/dist/consent/effectiveStatement.js.map +1 -0
- package/dist/consent/resolveConsentEntries.d.ts +24 -0
- package/dist/consent/resolveConsentEntries.js +40 -0
- package/dist/consent/resolveConsentEntries.js.map +1 -0
- package/dist/consent/resolveConsentSourcesRequest.d.ts +37 -0
- package/dist/consent/resolveConsentSourcesRequest.js +54 -0
- package/dist/consent/resolveConsentSourcesRequest.js.map +1 -0
- package/dist/consent/resolveConsentStatements.d.ts +45 -0
- package/dist/consent/resolveConsentStatements.js +46 -0
- package/dist/consent/resolveConsentStatements.js.map +1 -0
- package/dist/consent/resolvePublishedVersionRef.d.ts +17 -11
- package/dist/consent/resolvePublishedVersionRef.js +13 -10
- package/dist/consent/resolvePublishedVersionRef.js.map +1 -1
- package/dist/consent/types.d.ts +69 -0
- package/dist/context/formContext.d.ts +30 -0
- package/dist/context/formContext.js +54 -0
- package/dist/context/formContext.js.map +1 -0
- package/dist/email/departments.d.ts +59 -0
- package/dist/email/departments.js +87 -0
- package/dist/email/departments.js.map +1 -0
- package/dist/email/departmentsField.d.ts +53 -0
- package/dist/email/departmentsField.js +73 -0
- package/dist/email/departmentsField.js.map +1 -0
- package/dist/exports/client.d.ts +14 -1
- package/dist/exports/client.js +14 -1
- package/dist/exports/i18n.d.ts +5 -2
- package/dist/exports/i18n.js +5 -2
- package/dist/exports/react.d.ts +34 -10
- package/dist/exports/react.js +24 -4
- package/dist/exports/rsc.d.ts +10 -1
- package/dist/exports/rsc.js +10 -1
- package/dist/exports/types.d.ts +20 -12
- package/dist/fields/buildFieldBlocks.js +46 -25
- package/dist/fields/buildFieldBlocks.js.map +1 -1
- package/dist/fields/builtin/calculation.d.ts +17 -0
- package/dist/fields/builtin/calculation.js +193 -18
- package/dist/fields/builtin/calculation.js.map +1 -1
- package/dist/fields/builtin/checkbox.js +23 -0
- package/dist/fields/builtin/checkbox.js.map +1 -1
- package/dist/fields/builtin/consent.js +49 -14
- package/dist/fields/builtin/consent.js.map +1 -1
- package/dist/fields/builtin/country.d.ts +13 -0
- package/dist/fields/builtin/country.js +30 -0
- package/dist/fields/builtin/country.js.map +1 -0
- package/dist/fields/builtin/date.js +31 -0
- package/dist/fields/builtin/date.js.map +1 -0
- package/dist/fields/builtin/email.js +8 -1
- package/dist/fields/builtin/email.js.map +1 -1
- package/dist/fields/builtin/file.d.ts +15 -0
- package/dist/fields/builtin/file.js +95 -8
- package/dist/fields/builtin/file.js.map +1 -1
- package/dist/fields/builtin/index.d.ts +30 -0
- package/dist/fields/builtin/index.js +37 -10
- package/dist/fields/builtin/index.js.map +1 -1
- package/dist/fields/builtin/message.js +30 -0
- package/dist/fields/builtin/message.js.map +1 -0
- package/dist/fields/builtin/repeater.js +8 -3
- package/dist/fields/builtin/repeater.js.map +1 -1
- package/dist/fields/builtin/select.js +28 -4
- package/dist/fields/builtin/select.js.map +1 -1
- package/dist/fields/builtin/state.d.ts +13 -0
- package/dist/fields/builtin/state.js +30 -0
- package/dist/fields/builtin/state.js.map +1 -0
- package/dist/fields/builtin/text.js +7 -0
- package/dist/fields/builtin/text.js.map +1 -1
- package/dist/fields/data/regions.d.ts +21 -0
- package/dist/fields/data/regions.js +1238 -0
- package/dist/fields/data/regions.js.map +1 -0
- package/dist/fields/fieldKey.d.ts +15 -0
- package/dist/fields/fieldKey.js +16 -0
- package/dist/fields/fieldKey.js.map +1 -0
- package/dist/fields/fieldNamesOfType.js +30 -0
- package/dist/fields/fieldNamesOfType.js.map +1 -0
- package/dist/fields/instanceOptions.js +20 -0
- package/dist/fields/instanceOptions.js.map +1 -0
- package/dist/fields/localizedIf.d.ts +13 -0
- package/dist/fields/localizedIf.js +12 -0
- package/dist/fields/localizedIf.js.map +1 -0
- package/dist/fields/registry.d.ts +3 -1
- package/dist/fields/registry.js +23 -8
- package/dist/fields/registry.js.map +1 -1
- package/dist/fields/sharedConfig.js +125 -59
- package/dist/fields/sharedConfig.js.map +1 -1
- package/dist/fields/types.d.ts +78 -9
- package/dist/flow/engine.d.ts +6 -4
- package/dist/flow/engine.js +11 -6
- package/dist/flow/engine.js.map +1 -1
- package/dist/flow/normalizeFlow.js +28 -9
- package/dist/flow/normalizeFlow.js.map +1 -1
- package/dist/flow/stepLabel.js +15 -0
- package/dist/flow/stepLabel.js.map +1 -0
- package/dist/flow/types.d.ts +12 -3
- package/dist/flow/types.js +11 -0
- package/dist/flow/types.js.map +1 -0
- package/dist/form/pollState.d.ts +23 -0
- package/dist/form/pollState.js +17 -0
- package/dist/form/pollState.js.map +1 -0
- package/dist/form/toFormDocument.d.ts +110 -0
- package/dist/form/toFormDocument.js +89 -0
- package/dist/form/toFormDocument.js.map +1 -0
- package/dist/form/types.d.ts +73 -0
- package/dist/index.d.ts +67 -61
- package/dist/index.js +120 -25
- package/dist/index.js.map +1 -1
- package/dist/options.d.ts +281 -0
- package/dist/plugin/applyRegistryConfig.d.ts +11 -0
- package/dist/plugin/applyRegistryConfig.js +20 -0
- package/dist/plugin/applyRegistryConfig.js.map +1 -0
- package/dist/plugin/customState.js +26 -0
- package/dist/plugin/customState.js.map +1 -0
- package/dist/plugin/registerCollections.js +43 -40
- package/dist/plugin/registerCollections.js.map +1 -1
- package/dist/plugin/uploadsCollection.d.ts +16 -0
- package/dist/plugin/uploadsCollection.js +53 -0
- package/dist/plugin/uploadsCollection.js.map +1 -0
- package/dist/poll/applyPollOptions.js +18 -0
- package/dist/poll/applyPollOptions.js.map +1 -0
- package/dist/poll/buildPollOptionSourceFields.js +50 -0
- package/dist/poll/buildPollOptionSourceFields.js.map +1 -0
- package/dist/poll/closeJob.d.ts +53 -0
- package/dist/poll/closeJob.js +121 -0
- package/dist/poll/closeJob.js.map +1 -0
- package/dist/poll/definePollOptionSource.d.ts +44 -0
- package/dist/poll/definePollOptionSource.js +6 -0
- package/dist/poll/definePollOptionSource.js.map +1 -0
- package/dist/poll/definePollType.d.ts +34 -0
- package/dist/poll/definePollType.js +6 -0
- package/dist/poll/definePollType.js.map +1 -0
- package/dist/poll/effectivePollOptions.d.ts +38 -0
- package/dist/poll/effectivePollOptions.js +64 -0
- package/dist/poll/effectivePollOptions.js.map +1 -0
- package/dist/poll/mostVoted.d.ts +21 -0
- package/dist/poll/mostVoted.js +32 -0
- package/dist/poll/mostVoted.js.map +1 -0
- package/dist/poll/outcomeBeforeChange.js +91 -0
- package/dist/poll/outcomeBeforeChange.js.map +1 -0
- package/dist/poll/outcomeFields.d.ts +38 -0
- package/dist/poll/outcomeFields.js +52 -0
- package/dist/poll/outcomeFields.js.map +1 -0
- package/dist/poll/pollTypeRegistry.d.ts +34 -0
- package/dist/poll/pollTypeRegistry.js +69 -0
- package/dist/poll/pollTypeRegistry.js.map +1 -0
- package/dist/poll/registry.d.ts +17 -0
- package/dist/poll/registry.js +12 -0
- package/dist/poll/registry.js.map +1 -0
- package/dist/poll/resolvePollCloseRequest.js +106 -0
- package/dist/poll/resolvePollCloseRequest.js.map +1 -0
- package/dist/poll/resolvePollOptions.d.ts +29 -0
- package/dist/poll/resolvePollOptions.js +52 -0
- package/dist/poll/resolvePollOptions.js.map +1 -0
- package/dist/poll/resolvePollOptionsRequest.js +50 -0
- package/dist/poll/resolvePollOptionsRequest.js.map +1 -0
- package/dist/poll/resolvePollOutcome.d.ts +40 -0
- package/dist/poll/resolvePollOutcome.js +90 -0
- package/dist/poll/resolvePollOutcome.js.map +1 -0
- package/dist/poll/resultsField.js +21 -0
- package/dist/poll/resultsField.js.map +1 -0
- package/dist/poll/votes/aggregateFromVotes.d.ts +27 -0
- package/dist/poll/votes/aggregateFromVotes.js +54 -0
- package/dist/poll/votes/aggregateFromVotes.js.map +1 -0
- package/dist/poll/votes/answerValues.js +16 -0
- package/dist/poll/votes/answerValues.js.map +1 -0
- package/dist/poll/votes/bumpPollVote.js +88 -0
- package/dist/poll/votes/bumpPollVote.js.map +1 -0
- package/dist/poll/votes/recountPollVotes.d.ts +20 -0
- package/dist/poll/votes/recountPollVotes.js +63 -0
- package/dist/poll/votes/recountPollVotes.js.map +1 -0
- package/dist/poll/votes/voteTallyHook.js +76 -0
- package/dist/poll/votes/voteTallyHook.js.map +1 -0
- package/dist/poll/votes/votesCollection.d.ts +19 -0
- package/dist/poll/votes/votesCollection.js +89 -0
- package/dist/poll/votes/votesCollection.js.map +1 -0
- package/dist/prefill/valuesFromSearchParams.js +5 -9
- package/dist/prefill/valuesFromSearchParams.js.map +1 -1
- package/dist/presentations/types.d.ts +3 -2
- package/dist/react/Form.d.ts +77 -35
- package/dist/react/Form.js +368 -222
- package/dist/react/Form.js.map +1 -1
- package/dist/react/FormContext.d.ts +51 -1
- package/dist/react/FormContext.js.map +1 -1
- package/dist/react/FormControls.d.ts +55 -0
- package/dist/react/FormControls.js +56 -0
- package/dist/react/FormControls.js.map +1 -0
- package/dist/react/FormFields.d.ts +18 -0
- package/dist/react/FormFields.js +114 -0
- package/dist/react/FormFields.js.map +1 -0
- package/dist/react/FormLayout.d.ts +4 -2
- package/dist/react/FormLayout.js +3 -2
- package/dist/react/FormLayout.js.map +1 -1
- package/dist/react/FormResults.d.ts +8 -3
- package/dist/react/FormResults.js +50 -29
- package/dist/react/FormResults.js.map +1 -1
- package/dist/react/FormSteps.d.ts +17 -0
- package/dist/react/FormSteps.js +35 -0
- package/dist/react/FormSteps.js.map +1 -0
- package/dist/react/Poll.d.ts +40 -6
- package/dist/react/Poll.js +137 -33
- package/dist/react/Poll.js.map +1 -1
- package/dist/react/adapters.d.ts +35 -0
- package/dist/react/adapters.js +11 -0
- package/dist/react/adapters.js.map +1 -0
- package/dist/react/captcha/HcaptchaCaptcha.d.ts +31 -0
- package/dist/react/captcha/HcaptchaCaptcha.js +53 -0
- package/dist/react/captcha/HcaptchaCaptcha.js.map +1 -0
- package/dist/react/captcha/RecaptchaCaptcha.d.ts +44 -0
- package/dist/react/captcha/RecaptchaCaptcha.js +93 -0
- package/dist/react/captcha/RecaptchaCaptcha.js.map +1 -0
- package/dist/react/captcha/TurnstileCaptcha.d.ts +31 -0
- package/dist/react/captcha/TurnstileCaptcha.js +53 -0
- package/dist/react/captcha/TurnstileCaptcha.js.map +1 -0
- package/dist/react/captcha/types.d.ts +8 -0
- package/dist/react/captcha/useCaptchaScript.js +63 -0
- package/dist/react/captcha/useCaptchaScript.js.map +1 -0
- package/dist/react/captcha/vendors.js +8 -0
- package/dist/react/captcha/vendors.js.map +1 -0
- package/dist/react/contract.d.ts +2 -3
- package/dist/react/contract.js.map +1 -1
- package/dist/react/fetchResults.d.ts +1 -1
- package/dist/react/fetchResults.js +1 -1
- package/dist/react/fetchResults.js.map +1 -1
- package/dist/react/presentation/DialogSurface.js +1 -4
- package/dist/react/presentation/DialogSurface.js.map +1 -1
- package/dist/react/primitives/Checkbox.d.ts +6 -2
- package/dist/react/primitives/Checkbox.js +4 -2
- package/dist/react/primitives/Checkbox.js.map +1 -1
- package/dist/react/primitives/ChoiceGroup.d.ts +40 -0
- package/dist/react/primitives/ChoiceGroup.js +33 -0
- package/dist/react/primitives/ChoiceGroup.js.map +1 -0
- package/dist/react/primitives/FieldShell.d.ts +13 -5
- package/dist/react/primitives/FieldShell.js +35 -32
- package/dist/react/primitives/FieldShell.js.map +1 -1
- package/dist/react/primitives/Input.d.ts +4 -2
- package/dist/react/primitives/Input.js +2 -1
- package/dist/react/primitives/Input.js.map +1 -1
- package/dist/react/renderers/calculation.js +7 -5
- package/dist/react/renderers/calculation.js.map +1 -1
- package/dist/react/renderers/checkbox.js +3 -5
- package/dist/react/renderers/checkbox.js.map +1 -1
- package/dist/react/renderers/consent.js +84 -36
- package/dist/react/renderers/consent.js.map +1 -1
- package/dist/react/renderers/date.js +33 -0
- package/dist/react/renderers/date.js.map +1 -0
- package/dist/react/renderers/email.js +4 -5
- package/dist/react/renderers/email.js.map +1 -1
- package/dist/react/renderers/file.js +29 -14
- package/dist/react/renderers/file.js.map +1 -1
- package/dist/react/renderers/index.js +7 -0
- package/dist/react/renderers/index.js.map +1 -1
- package/dist/react/renderers/message.js +41 -0
- package/dist/react/renderers/message.js.map +1 -0
- package/dist/react/renderers/number.js +1 -4
- package/dist/react/renderers/number.js.map +1 -1
- package/dist/react/renderers/region.js +43 -0
- package/dist/react/renderers/region.js.map +1 -0
- package/dist/react/renderers/repeater.js +32 -14
- package/dist/react/renderers/repeater.js.map +1 -1
- package/dist/react/renderers/select.js +21 -6
- package/dist/react/renderers/select.js.map +1 -1
- package/dist/react/renderers/text.js +4 -5
- package/dist/react/renderers/text.js.map +1 -1
- package/dist/react/renderers/textarea.js +1 -4
- package/dist/react/renderers/textarea.js.map +1 -1
- package/dist/react/state.d.ts +46 -3
- package/dist/react/state.js +73 -10
- package/dist/react/state.js.map +1 -1
- package/dist/react/submitForm.js +1 -1
- package/dist/react/submitForm.js.map +1 -1
- package/dist/react/uploadFile.d.ts +2 -2
- package/dist/react/uploadFile.js +1 -1
- package/dist/react/uploadFile.js.map +1 -1
- package/dist/react/useCalcValues.d.ts +10 -0
- package/dist/react/useCalcValues.js +13 -0
- package/dist/react/useCalcValues.js.map +1 -0
- package/dist/react/useField.d.ts +0 -1
- package/dist/react/useField.js +7 -6
- package/dist/react/useField.js.map +1 -1
- package/dist/react/useFormState.d.ts +1 -1
- package/dist/react/useFormState.js +1 -1
- package/dist/react/useFormState.js.map +1 -1
- package/dist/react/validateField.js +3 -6
- package/dist/react/validateField.js.map +1 -1
- package/dist/recall/interpolate.d.ts +4 -3
- package/dist/recall/interpolate.js +5 -4
- package/dist/recall/interpolate.js.map +1 -1
- package/dist/recall/resolver.d.ts +1 -1
- package/dist/recall/resolver.js +15 -3
- package/dist/recall/resolver.js.map +1 -1
- package/dist/spam/captcha.d.ts +4 -3
- package/dist/spam/captcha.js +4 -3
- package/dist/spam/captcha.js.map +1 -1
- package/dist/spam/clientIp.js +9 -0
- package/dist/spam/clientIp.js.map +1 -0
- package/dist/spam/constants.js +13 -1
- package/dist/spam/constants.js.map +1 -1
- package/dist/spam/identify.js +3 -6
- package/dist/spam/identify.js.map +1 -1
- package/dist/spam/providers/hcaptcha.d.ts +17 -0
- package/dist/spam/providers/hcaptcha.js +18 -0
- package/dist/spam/providers/hcaptcha.js.map +1 -0
- package/dist/spam/providers/recaptcha.d.ts +23 -0
- package/dist/spam/providers/recaptcha.js +23 -0
- package/dist/spam/providers/recaptcha.js.map +1 -0
- package/dist/spam/providers/siteverify.js +34 -0
- package/dist/spam/providers/siteverify.js.map +1 -0
- package/dist/spam/providers/turnstile.d.ts +17 -0
- package/dist/spam/providers/turnstile.js +18 -0
- package/dist/spam/providers/turnstile.js.map +1 -0
- package/dist/spam/resolveSpam.js +2 -1
- package/dist/spam/resolveSpam.js.map +1 -1
- package/dist/spam/spamGuard.js +25 -11
- package/dist/spam/spamGuard.js.map +1 -1
- package/dist/spam/types.d.ts +19 -4
- package/dist/submissions/SubmissionAnswers.js +6 -2
- package/dist/submissions/SubmissionAnswers.js.map +1 -1
- package/dist/submissions/SubmissionAnswersClient.js +100 -64
- package/dist/submissions/SubmissionAnswersClient.js.map +1 -1
- package/dist/submissions/coerceBoolean.js +14 -0
- package/dist/submissions/coerceBoolean.js.map +1 -0
- package/dist/submissions/createSubmission.d.ts +36 -0
- package/dist/submissions/createSubmission.js +26 -0
- package/dist/submissions/createSubmission.js.map +1 -0
- package/dist/submissions/formIdOf.js +13 -0
- package/dist/submissions/formIdOf.js.map +1 -0
- package/dist/submissions/resolveVotedSubmission.d.ts +27 -0
- package/dist/submissions/resolveVotedSubmission.js +51 -0
- package/dist/submissions/resolveVotedSubmission.js.map +1 -0
- package/dist/submissions/runSubmission.d.ts +10 -0
- package/dist/submissions/runSubmission.js +104 -54
- package/dist/submissions/runSubmission.js.map +1 -1
- package/dist/submissions/types.d.ts +11 -4
- package/dist/submissions/validateSubmission.js +99 -13
- package/dist/submissions/validateSubmission.js.map +1 -1
- package/dist/submissions/verifyContext.js +35 -0
- package/dist/submissions/verifyContext.js.map +1 -0
- package/dist/submissions/voteChangeEndpoint.js +105 -0
- package/dist/submissions/voteChangeEndpoint.js.map +1 -0
- package/dist/submissions/votedCookie.d.ts +15 -0
- package/dist/submissions/votedCookie.js +74 -0
- package/dist/submissions/votedCookie.js.map +1 -0
- package/dist/translations/clientRuntimeKeys.d.ts +17 -0
- package/dist/translations/clientRuntimeKeys.js +48 -0
- package/dist/translations/clientRuntimeKeys.js.map +1 -0
- package/dist/translations/de.d.ts +12 -0
- package/dist/translations/de.js +380 -0
- package/dist/translations/de.js.map +1 -0
- package/dist/translations/en.js +250 -28
- package/dist/translations/en.js.map +1 -1
- package/dist/translations/index.d.ts +14 -5
- package/dist/translations/index.js +14 -3
- package/dist/translations/index.js.map +1 -1
- package/dist/translations/keys.d.ts +245 -23
- package/dist/translations/keys.js +246 -24
- package/dist/translations/keys.js.map +1 -1
- package/dist/translations/makeTranslate.d.ts +7 -3
- package/dist/translations/makeTranslate.js +12 -3
- package/dist/translations/makeTranslate.js.map +1 -1
- package/dist/translations/server.js +8 -1
- package/dist/translations/server.js.map +1 -1
- package/dist/uploads/captureFileRef.d.ts +10 -3
- package/dist/uploads/captureFileRef.js +18 -9
- package/dist/uploads/captureFileRef.js.map +1 -1
- package/dist/uploads/formatBytes.d.ts +9 -0
- package/dist/uploads/formatBytes.js +25 -0
- package/dist/uploads/formatBytes.js.map +1 -0
- package/dist/uploads/resolveFileRef.js +3 -2
- package/dist/uploads/resolveFileRef.js.map +1 -1
- package/dist/uploads/types.d.ts +1 -2
- package/dist/validation/buildRuleBlocks.js +18 -17
- package/dist/validation/buildRuleBlocks.js.map +1 -1
- package/dist/validation/builtin/email.js +2 -1
- package/dist/validation/builtin/email.js.map +1 -1
- package/dist/validation/builtin/index.d.ts +14 -0
- package/dist/validation/builtin/index.js +14 -1
- package/dist/validation/builtin/index.js.map +1 -1
- package/dist/validation/builtin/integer.js +15 -0
- package/dist/validation/builtin/integer.js.map +1 -0
- package/dist/validation/builtin/matchesField.js +3 -7
- package/dist/validation/builtin/matchesField.js.map +1 -1
- package/dist/validation/builtin/max.js +1 -0
- package/dist/validation/builtin/max.js.map +1 -1
- package/dist/validation/builtin/maxDate.js +24 -0
- package/dist/validation/builtin/maxDate.js.map +1 -0
- package/dist/validation/builtin/maxLength.js +1 -0
- package/dist/validation/builtin/maxLength.js.map +1 -1
- package/dist/validation/builtin/min.js +1 -0
- package/dist/validation/builtin/min.js.map +1 -1
- package/dist/validation/builtin/minDate.js +24 -0
- package/dist/validation/builtin/minDate.js.map +1 -0
- package/dist/validation/builtin/minLength.js +1 -0
- package/dist/validation/builtin/minLength.js.map +1 -1
- package/dist/validation/builtin/notAlreadySubmitted.js +5 -1
- package/dist/validation/builtin/notAlreadySubmitted.js.map +1 -1
- package/dist/validation/builtin/oneOf.js +1 -0
- package/dist/validation/builtin/oneOf.js.map +1 -1
- package/dist/validation/builtin/pattern.js +11 -2
- package/dist/validation/builtin/pattern.js.map +1 -1
- package/dist/validation/builtin/url.js +1 -0
- package/dist/validation/builtin/url.js.map +1 -1
- package/dist/validation/builtin/validateDateBound.js +18 -0
- package/dist/validation/builtin/validateDateBound.js.map +1 -0
- package/dist/validation/builtin/validateRegexParams.js +44 -0
- package/dist/validation/builtin/validateRegexParams.js.map +1 -0
- package/dist/validation/emailPattern.js +10 -0
- package/dist/validation/emailPattern.js.map +1 -0
- package/dist/validation/fieldTargetParam.d.ts +22 -0
- package/dist/validation/fieldTargetParam.js +38 -0
- package/dist/validation/fieldTargetParam.js.map +1 -0
- package/dist/validation/registry.d.ts +2 -1
- package/dist/validation/registry.js +2 -9
- package/dist/validation/registry.js.map +1 -1
- package/dist/validation/runValidation.js +8 -29
- package/dist/validation/runValidation.js.map +1 -1
- package/dist/validation/types.d.ts +5 -11
- package/dist/validation/validateUrl.js +22 -0
- package/dist/validation/validateUrl.js.map +1 -0
- package/package.json +14 -6
- package/styles/form-builder.css +94 -190
- package/dist/collections/uploads.d.ts +0 -30
- package/dist/collections/uploads.js +0 -55
- package/dist/collections/uploads.js.map +0 -1
- package/dist/consent/buildConsentSourceConfig.js +0 -49
- package/dist/consent/buildConsentSourceConfig.js.map +0 -1
- package/dist/consent/builtin/index.d.ts +0 -7
- package/dist/consent/builtin/index.js +0 -11
- package/dist/consent/builtin/index.js.map +0 -1
- package/dist/consent/builtin/pageReference.js +0 -75
- package/dist/consent/builtin/pageReference.js.map +0 -1
- package/dist/consent/builtin/static.js +0 -44
- package/dist/consent/builtin/static.js.map +0 -1
- package/dist/consent/defineConsentSource.d.ts +0 -35
- package/dist/consent/defineConsentSource.js +0 -6
- package/dist/consent/defineConsentSource.js.map +0 -1
- package/dist/consent/registry.d.ts +0 -16
- package/dist/consent/registry.js +0 -16
- package/dist/consent/registry.js.map +0 -1
- package/dist/consent/resolveConsentLinks.d.ts +0 -19
- package/dist/consent/resolveConsentLinks.js +0 -26
- package/dist/consent/resolveConsentLinks.js.map +0 -1
- package/dist/presentations/registry.d.ts +0 -15
- package/dist/presentations/registry.js +0 -15
- package/dist/presentations/registry.js.map +0 -1
- package/dist/react/toFormDocument.d.ts +0 -24
- package/dist/react/toFormDocument.js +0 -22
- package/dist/react/toFormDocument.js.map +0 -1
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { keys } from "../translations/keys.js";
|
|
2
|
+
import { labelForKey, resolveDefinitionLabel } from "../translations/server.js";
|
|
3
|
+
//#region src/poll/buildPollOptionSourceFields.ts
|
|
4
|
+
/**
|
|
5
|
+
* Builds the `optionSource` select and `sourceConfig` group for the forms poll group; returns
|
|
6
|
+
* nothing when no sources are registered, so the fields only exist for hosts that opted in.
|
|
7
|
+
* Fields inside `sourceConfig` are annotated with `admin.condition` so only the fields of the
|
|
8
|
+
* currently selected source type are visible; like consent's `sourceConfig`, field names within
|
|
9
|
+
* the group must be unique across all registered sources. Unlike consent's `source` select there
|
|
10
|
+
* is no default: hand-authored options remain the baseline and a source is an explicit opt-in per
|
|
11
|
+
* form (the select stays clearable).
|
|
12
|
+
*/
|
|
13
|
+
const buildPollOptionSourceFields = (registry) => {
|
|
14
|
+
const sources = [...registry.values()];
|
|
15
|
+
if (sources.length === 0) return [];
|
|
16
|
+
const sourceConfigFields = sources.flatMap((source) => (source.config ?? []).map((field) => {
|
|
17
|
+
const existingAdmin = typeof field.admin === "object" && field.admin !== null ? field.admin : {};
|
|
18
|
+
return {
|
|
19
|
+
...field,
|
|
20
|
+
admin: {
|
|
21
|
+
...existingAdmin,
|
|
22
|
+
condition: (data) => data?.poll?.optionSource === source.type
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
}));
|
|
26
|
+
return [{
|
|
27
|
+
name: "optionSource",
|
|
28
|
+
type: "select",
|
|
29
|
+
label: labelForKey(keys.pollOptionSource),
|
|
30
|
+
admin: { description: labelForKey(keys.pollOptionSourceDescription) },
|
|
31
|
+
options: sources.map((source) => ({
|
|
32
|
+
label: resolveDefinitionLabel(source.label),
|
|
33
|
+
value: source.type
|
|
34
|
+
}))
|
|
35
|
+
}, {
|
|
36
|
+
name: "sourceConfig",
|
|
37
|
+
type: "group",
|
|
38
|
+
label: labelForKey(keys.pollSourceConfig),
|
|
39
|
+
admin: { condition: (_data, siblingData) => Boolean(siblingData?.optionSource) },
|
|
40
|
+
fields: sourceConfigFields.length > 0 ? sourceConfigFields : [{
|
|
41
|
+
name: "_placeholder",
|
|
42
|
+
type: "text",
|
|
43
|
+
admin: { hidden: true }
|
|
44
|
+
}]
|
|
45
|
+
}];
|
|
46
|
+
};
|
|
47
|
+
//#endregion
|
|
48
|
+
export { buildPollOptionSourceFields };
|
|
49
|
+
|
|
50
|
+
//# sourceMappingURL=buildPollOptionSourceFields.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buildPollOptionSourceFields.js","names":[],"sources":["../../src/poll/buildPollOptionSourceFields.ts"],"sourcesContent":["import type { Field } from 'payload'\nimport { keys } from '../translations/keys'\nimport { labelForKey, resolveDefinitionLabel } from '../translations/server'\nimport type { PollOptionSourceRegistry } from './registry'\n\n/**\n * Builds the `optionSource` select and `sourceConfig` group for the forms poll group; returns\n * nothing when no sources are registered, so the fields only exist for hosts that opted in.\n * Fields inside `sourceConfig` are annotated with `admin.condition` so only the fields of the\n * currently selected source type are visible; like consent's `sourceConfig`, field names within\n * the group must be unique across all registered sources. Unlike consent's `source` select there\n * is no default: hand-authored options remain the baseline and a source is an explicit opt-in per\n * form (the select stays clearable).\n */\nexport const buildPollOptionSourceFields = (registry: PollOptionSourceRegistry): Field[] => {\n\tconst sources = [...registry.values()]\n\tif (sources.length === 0) {\n\t\treturn []\n\t}\n\n\tconst sourceConfigFields: Field[] = sources.flatMap((source) =>\n\t\t(source.config ?? []).map((field) => {\n\t\t\tconst existingAdmin =\n\t\t\t\ttypeof field.admin === 'object' && field.admin !== null ? field.admin : {}\n\t\t\treturn {\n\t\t\t\t...field,\n\t\t\t\tadmin: {\n\t\t\t\t\t...existingAdmin,\n\t\t\t\t\tcondition: (data: unknown) =>\n\t\t\t\t\t\t(data as { poll?: { optionSource?: string } })?.poll?.optionSource === source.type,\n\t\t\t\t},\n\t\t\t} as Field\n\t\t})\n\t)\n\n\treturn [\n\t\t{\n\t\t\tname: 'optionSource',\n\t\t\ttype: 'select',\n\t\t\tlabel: labelForKey(keys.pollOptionSource),\n\t\t\tadmin: {\n\t\t\t\tdescription: labelForKey(keys.pollOptionSourceDescription),\n\t\t\t},\n\t\t\toptions: sources.map((source) => ({\n\t\t\t\tlabel: resolveDefinitionLabel(source.label),\n\t\t\t\tvalue: source.type,\n\t\t\t})),\n\t\t},\n\t\t{\n\t\t\tname: 'sourceConfig',\n\t\t\ttype: 'group',\n\t\t\tlabel: labelForKey(keys.pollSourceConfig),\n\t\t\tadmin: {\n\t\t\t\tcondition: (_data, siblingData) => Boolean(siblingData?.optionSource),\n\t\t\t},\n\t\t\tfields:\n\t\t\t\tsourceConfigFields.length > 0\n\t\t\t\t\t? sourceConfigFields\n\t\t\t\t\t: [{ name: '_placeholder', type: 'text', admin: { hidden: true } }],\n\t\t},\n\t]\n}\n"],"mappings":";;;;;;;;;;;;AAcA,MAAa,+BAA+B,aAAgD;CAC3F,MAAM,UAAU,CAAC,GAAG,SAAS,OAAO,CAAC;CACrC,IAAI,QAAQ,WAAW,GACtB,OAAO,CAAC;CAGT,MAAM,qBAA8B,QAAQ,SAAS,YACnD,OAAO,UAAU,CAAC,GAAG,KAAK,UAAU;EACpC,MAAM,gBACL,OAAO,MAAM,UAAU,YAAY,MAAM,UAAU,OAAO,MAAM,QAAQ,CAAC;EAC1E,OAAO;GACN,GAAG;GACH,OAAO;IACN,GAAG;IACH,YAAY,SACV,MAA+C,MAAM,iBAAiB,OAAO;GAChF;EACD;CACD,CAAC,CACF;CAEA,OAAO,CACN;EACC,MAAM;EACN,MAAM;EACN,OAAO,YAAY,KAAK,gBAAgB;EACxC,OAAO,EACN,aAAa,YAAY,KAAK,2BAA2B,EAC1D;EACA,SAAS,QAAQ,KAAK,YAAY;GACjC,OAAO,uBAAuB,OAAO,KAAK;GAC1C,OAAO,OAAO;EACf,EAAE;CACH,GACA;EACC,MAAM;EACN,MAAM;EACN,OAAO,YAAY,KAAK,gBAAgB;EACxC,OAAO,EACN,YAAY,OAAO,gBAAgB,QAAQ,aAAa,YAAY,EACrE;EACA,QACC,mBAAmB,SAAS,IACzB,qBACA,CAAC;GAAE,MAAM;GAAgB,MAAM;GAAQ,OAAO,EAAE,QAAQ,KAAK;EAAE,CAAC;CACrE,CACD;AACD"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Config, Payload, PayloadRequest, TaskConfig } from "payload";
|
|
2
|
+
|
|
3
|
+
//#region src/poll/closeJob.d.ts
|
|
4
|
+
declare const POLL_CLOSE_TASK_SLUG = "form-builder-poll-close";
|
|
5
|
+
/** Input the enqueue path stores; the handler re-loads everything else from the DB. */
|
|
6
|
+
type PollCloseTaskInput = {
|
|
7
|
+
formId: number | string;
|
|
8
|
+
};
|
|
9
|
+
/** The slice of a loaded form the close gate reads. */
|
|
10
|
+
type PollFormLike = {
|
|
11
|
+
pollEnabled?: boolean | null;
|
|
12
|
+
poll?: unknown;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* True when a poll's outcome should auto-resolve now: enabled, closed, still unresolved, and its
|
|
16
|
+
* strategy is not `manual`. Shared by the close-job handler and the results-read fallback so the two
|
|
17
|
+
* triggers apply the exact same gate.
|
|
18
|
+
*/
|
|
19
|
+
declare const shouldAutoResolvePoll: (form: PollFormLike) => boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Run the scheduled close for one form. Idempotent: re-loads the form and only resolves when the
|
|
22
|
+
* poll is enabled, closed, still unresolved, and not `manual`, so a re-run after the outcome was
|
|
23
|
+
* recorded (or a job that fires slightly early) is a safe no-op. Tolerates a deleted form.
|
|
24
|
+
*/
|
|
25
|
+
declare const runPollClose: (args: {
|
|
26
|
+
payload: Payload;
|
|
27
|
+
input: PollCloseTaskInput; /** Whether the hidden tally store backs the outcome aggregation (see `resolvePollOutcome`). */
|
|
28
|
+
pollVotesEnabled?: boolean;
|
|
29
|
+
req?: PayloadRequest;
|
|
30
|
+
}) => Promise<void>;
|
|
31
|
+
/** Native Payload jobs task that resolves a poll's outcome once it closes. */
|
|
32
|
+
declare const buildPollCloseTask: (pollVotesEnabled?: boolean) => TaskConfig;
|
|
33
|
+
/** Register the poll-close task on `config.jobs.tasks`, creating the jobs config if absent. */
|
|
34
|
+
declare const registerPollCloseTask: (config: Config, pollVotesEnabled?: boolean) => void;
|
|
35
|
+
/**
|
|
36
|
+
* Schedule (or reschedule) the auto-close for a poll after a save. Enqueues a `waitUntil: closesAt`
|
|
37
|
+
* job when the poll is enabled, has a `closesAt`, is still unresolved, and its strategy is not
|
|
38
|
+
* `manual`, and only when a job runner is present (`payload.jobs.queue`); otherwise the resolve-on-read
|
|
39
|
+
* fallback is the safety net. Any prior pending close job for the form is deleted first so the newest
|
|
40
|
+
* `closesAt` wins. Never throws; a queue failure is logged and swallowed.
|
|
41
|
+
*/
|
|
42
|
+
declare const enqueuePollClose: (args: {
|
|
43
|
+
payload: Payload;
|
|
44
|
+
form: {
|
|
45
|
+
id: number | string;
|
|
46
|
+
pollEnabled?: boolean | null;
|
|
47
|
+
poll?: unknown;
|
|
48
|
+
};
|
|
49
|
+
req?: PayloadRequest;
|
|
50
|
+
}) => Promise<void>;
|
|
51
|
+
//#endregion
|
|
52
|
+
export { POLL_CLOSE_TASK_SLUG, PollCloseTaskInput, buildPollCloseTask, enqueuePollClose, registerPollCloseTask, runPollClose, shouldAutoResolvePoll };
|
|
53
|
+
//# sourceMappingURL=closeJob.d.ts.map
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { isPollClosed, pollConfigOf } from "../form/pollState.js";
|
|
2
|
+
import { resolvePollOutcome } from "./resolvePollOutcome.js";
|
|
3
|
+
import { FORMS_SLUG } from "../collections/forms.js";
|
|
4
|
+
//#region src/poll/closeJob.ts
|
|
5
|
+
const POLL_CLOSE_TASK_SLUG = "form-builder-poll-close";
|
|
6
|
+
/** Payload's jobs collection; queried directly to supersede a superseded pending close job. */
|
|
7
|
+
const JOBS_SLUG = "payload-jobs";
|
|
8
|
+
const hasEmptyWinners = (poll) => {
|
|
9
|
+
const winners = poll.outcome?.winningValues;
|
|
10
|
+
return !Array.isArray(winners) || winners.filter((value) => typeof value === "string" && value.length > 0).length === 0;
|
|
11
|
+
};
|
|
12
|
+
const strategyOf = (poll) => typeof poll.type === "string" && poll.type.length > 0 ? poll.type : "manual";
|
|
13
|
+
/**
|
|
14
|
+
* True when a poll's outcome should auto-resolve now: enabled, closed, still unresolved, and its
|
|
15
|
+
* strategy is not `manual`. Shared by the close-job handler and the results-read fallback so the two
|
|
16
|
+
* triggers apply the exact same gate.
|
|
17
|
+
*/
|
|
18
|
+
const shouldAutoResolvePoll = (form) => {
|
|
19
|
+
const poll = pollConfigOf(form.poll);
|
|
20
|
+
return form.pollEnabled === true && poll != null && isPollClosed(poll) && hasEmptyWinners(poll) && strategyOf(poll) !== "manual";
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Run the scheduled close for one form. Idempotent: re-loads the form and only resolves when the
|
|
24
|
+
* poll is enabled, closed, still unresolved, and not `manual`, so a re-run after the outcome was
|
|
25
|
+
* recorded (or a job that fires slightly early) is a safe no-op. Tolerates a deleted form.
|
|
26
|
+
*/
|
|
27
|
+
const runPollClose = async (args) => {
|
|
28
|
+
const { payload, input, pollVotesEnabled, req } = args;
|
|
29
|
+
const form = await payload.findByID({
|
|
30
|
+
collection: FORMS_SLUG,
|
|
31
|
+
id: input.formId,
|
|
32
|
+
depth: 0,
|
|
33
|
+
overrideAccess: true,
|
|
34
|
+
req
|
|
35
|
+
}).catch(() => null);
|
|
36
|
+
if (!form || !shouldAutoResolvePoll(form)) return;
|
|
37
|
+
await resolvePollOutcome({
|
|
38
|
+
payload,
|
|
39
|
+
formId: input.formId,
|
|
40
|
+
pollVotesEnabled,
|
|
41
|
+
req
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
/** Native Payload jobs task that resolves a poll's outcome once it closes. */
|
|
45
|
+
const buildPollCloseTask = (pollVotesEnabled = false) => ({
|
|
46
|
+
slug: POLL_CLOSE_TASK_SLUG,
|
|
47
|
+
inputSchema: [{
|
|
48
|
+
name: "formId",
|
|
49
|
+
type: "text",
|
|
50
|
+
required: true
|
|
51
|
+
}],
|
|
52
|
+
handler: async ({ input, req }) => {
|
|
53
|
+
await runPollClose({
|
|
54
|
+
payload: req.payload,
|
|
55
|
+
input,
|
|
56
|
+
pollVotesEnabled,
|
|
57
|
+
req
|
|
58
|
+
});
|
|
59
|
+
return { output: {} };
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
/** Register the poll-close task on `config.jobs.tasks`, creating the jobs config if absent. */
|
|
63
|
+
const registerPollCloseTask = (config, pollVotesEnabled = false) => {
|
|
64
|
+
config.jobs ??= {};
|
|
65
|
+
config.jobs.tasks ??= [];
|
|
66
|
+
config.jobs.tasks.push(buildPollCloseTask(pollVotesEnabled));
|
|
67
|
+
};
|
|
68
|
+
const canQueue = (payload) => typeof payload.jobs?.queue === "function";
|
|
69
|
+
/**
|
|
70
|
+
* Delete this form's pending (never-run) close jobs so a re-enqueue supersedes the prior one. Payload
|
|
71
|
+
* 3.85's `jobs.queue` has no concurrency key, so this mirrors core's `deleteScheduledPublishJobs`:
|
|
72
|
+
* a direct `db.deleteMany` on the jobs collection, scoped by task slug and the JSON `input.formId`.
|
|
73
|
+
* Best-effort; a failure here only risks a duplicate future job, which the idempotent handler absorbs.
|
|
74
|
+
*/
|
|
75
|
+
const deletePendingCloseJobs = async (payload, formId, req) => {
|
|
76
|
+
try {
|
|
77
|
+
await payload.db.deleteMany({
|
|
78
|
+
collection: JOBS_SLUG,
|
|
79
|
+
req,
|
|
80
|
+
where: { and: [
|
|
81
|
+
{ completedAt: { exists: false } },
|
|
82
|
+
{ processing: { equals: false } },
|
|
83
|
+
{ taskSlug: { equals: POLL_CLOSE_TASK_SLUG } },
|
|
84
|
+
{ "input.formId": { equals: String(formId) } }
|
|
85
|
+
] }
|
|
86
|
+
});
|
|
87
|
+
} catch (error) {
|
|
88
|
+
payload.logger?.error(`@10x-media/form-builder: failed to clear pending poll-close jobs for form ${String(formId)}: ${error instanceof Error ? error.message : String(error)}`);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Schedule (or reschedule) the auto-close for a poll after a save. Enqueues a `waitUntil: closesAt`
|
|
93
|
+
* job when the poll is enabled, has a `closesAt`, is still unresolved, and its strategy is not
|
|
94
|
+
* `manual`, and only when a job runner is present (`payload.jobs.queue`); otherwise the resolve-on-read
|
|
95
|
+
* fallback is the safety net. Any prior pending close job for the form is deleted first so the newest
|
|
96
|
+
* `closesAt` wins. Never throws; a queue failure is logged and swallowed.
|
|
97
|
+
*/
|
|
98
|
+
const enqueuePollClose = async (args) => {
|
|
99
|
+
const { payload, form, req } = args;
|
|
100
|
+
const poll = pollConfigOf(form.poll);
|
|
101
|
+
if (form.pollEnabled !== true || !poll) return;
|
|
102
|
+
const closesAt = typeof poll.closesAt === "string" && poll.closesAt.length > 0 ? poll.closesAt : void 0;
|
|
103
|
+
if (!closesAt || !hasEmptyWinners(poll) || strategyOf(poll) === "manual" || !canQueue(payload)) return;
|
|
104
|
+
const waitUntil = new Date(closesAt);
|
|
105
|
+
if (Number.isNaN(waitUntil.getTime())) return;
|
|
106
|
+
await deletePendingCloseJobs(payload, form.id, req);
|
|
107
|
+
try {
|
|
108
|
+
await payload.jobs.queue({
|
|
109
|
+
task: POLL_CLOSE_TASK_SLUG,
|
|
110
|
+
input: { formId: String(form.id) },
|
|
111
|
+
waitUntil,
|
|
112
|
+
req
|
|
113
|
+
});
|
|
114
|
+
} catch (error) {
|
|
115
|
+
payload.logger?.error(`@10x-media/form-builder: failed to enqueue poll-close for form ${String(form.id)}: ${error instanceof Error ? error.message : String(error)}`);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
//#endregion
|
|
119
|
+
export { POLL_CLOSE_TASK_SLUG, buildPollCloseTask, enqueuePollClose, registerPollCloseTask, runPollClose, shouldAutoResolvePoll };
|
|
120
|
+
|
|
121
|
+
//# sourceMappingURL=closeJob.js.map
|
|
@@ -0,0 +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":";;;;AAKA,MAAa,uBAAuB;;AAGpC,MAAM,YAAY;AAQlB,MAAM,mBAAmB,SAAkC;CAC1D,MAAM,UAAU,KAAK,SAAS;CAC9B,OACC,CAAC,MAAM,QAAQ,OAAO,KACtB,QAAQ,QAAQ,UAAU,OAAO,UAAU,YAAY,MAAM,SAAS,CAAC,EAAE,WAAW;AAEtF;AAEA,MAAM,cAAc,SACnB,OAAO,KAAK,SAAS,YAAY,KAAK,KAAK,SAAS,IAAI,KAAK,OAAO;;;;;;AAOrE,MAAa,yBAAyB,SAAgC;CACrE,MAAM,OAAO,aAAa,KAAK,IAAI;CACnC,OACC,KAAK,gBAAgB,QACrB,QAAQ,QACR,aAAa,IAAI,KACjB,gBAAgB,IAAI,KACpB,WAAW,IAAI,MAAM;AAEvB;;;;;;AAOA,MAAa,eAAe,OAAO,SAMd;CACpB,MAAM,EAAE,SAAS,OAAO,kBAAkB,QAAQ;CAClD,MAAM,OAAO,MAAM,QACjB,SAAS;EAAE,YAAY;EAAY,IAAI,MAAM;EAAQ,OAAO;EAAG,gBAAgB;EAAM;CAAI,CAAC,EAC1F,YAAY,IAAI;CAClB,IAAI,CAAC,QAAQ,CAAC,sBAAsB,IAAoB,GACvD;CAED,MAAM,mBAAmB;EAAE;EAAS,QAAQ,MAAM;EAAQ;EAAkB;CAAI,CAAC;AAClF;;AAGA,MAAa,sBAAsB,mBAAmB,WACpD;CACA,MAAM;CACN,aAAa,CAAC;EAAE,MAAM;EAAU,MAAM;EAAQ,UAAU;CAAK,CAAC;CAC9D,SAAS,OAAO,EAAE,OAAO,UAAU;EAClC,MAAM,aAAa;GAClB,SAAS,IAAI;GACN;GACP;GACA;EACD,CAAC;EACD,OAAO,EAAE,QAAQ,CAAC,EAAE;CACrB;AACD;;AAGD,MAAa,yBAAyB,QAAgB,mBAAmB,UAAgB;CACxF,OAAO,SAAS,CAAC;CACjB,OAAO,KAAK,UAAU,CAAC;CACvB,OAAO,KAAK,MAAM,KAAK,mBAAmB,gBAAgB,CAAC;AAC5D;AAEA,MAAM,YAAY,YAA8B,OAAO,QAAQ,MAAM,UAAU;;;;;;;AAQ/E,MAAM,yBAAyB,OAC9B,SACA,QACA,QACmB;CACnB,IAAI;EACH,MAAM,QAAQ,GAAG,WAAW;GAC3B,YAAY;GACZ;GACA,OAAO,EACN,KAAK;IACJ,EAAE,aAAa,EAAE,QAAQ,MAAM,EAAE;IACjC,EAAE,YAAY,EAAE,QAAQ,MAAM,EAAE;IAChC,EAAE,UAAU,EAAE,QAAQ,qBAAqB,EAAE;IAC7C,EAAE,gBAAgB,EAAE,QAAQ,OAAO,MAAM,EAAE,EAAE;GAC9C,EACD;EACD,CAAC;CACF,SAAS,OAAO;EACf,QAAQ,QAAQ,MACf,6EAA6E,OAAO,MAAM,EAAE,IAC3F,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAEvD;CACD;AACD;;;;;;;;AASA,MAAa,mBAAmB,OAAO,SAIlB;CACpB,MAAM,EAAE,SAAS,MAAM,QAAQ;CAC/B,MAAM,OAAO,aAAa,KAAK,IAAI;CACnC,IAAI,KAAK,gBAAgB,QAAQ,CAAC,MACjC;CAED,MAAM,WACL,OAAO,KAAK,aAAa,YAAY,KAAK,SAAS,SAAS,IAAI,KAAK,WAAW,KAAA;CACjF,IAAI,CAAC,YAAY,CAAC,gBAAgB,IAAI,KAAK,WAAW,IAAI,MAAM,YAAY,CAAC,SAAS,OAAO,GAC5F;CAED,MAAM,YAAY,IAAI,KAAK,QAAQ;CACnC,IAAI,OAAO,MAAM,UAAU,QAAQ,CAAC,GACnC;CAED,MAAM,uBAAuB,SAAS,KAAK,IAAI,GAAG;CAClD,IAAI;EACH,MAAM,QAAQ,KAAK,MAAM;GACxB,MAAM;GACN,OAAO,EAAE,QAAQ,OAAO,KAAK,EAAE,EAAE;GACjC;GACA;EACD,CAAC;CACF,SAAS,OAAO;EACf,QAAQ,QAAQ,MACf,kEAAkE,OAAO,KAAK,EAAE,EAAE,IACjF,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAEvD;CACD;AACD"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Field, Payload, PayloadRequest } from "payload";
|
|
2
|
+
|
|
3
|
+
//#region src/poll/definePollOptionSource.d.ts
|
|
4
|
+
/** One selectable poll choice. `value` is the host's stable identifier contract: it is stored in submissions and later matched against `outcome.winningValues`, so it must never change once votes exist. */
|
|
5
|
+
type PollOption = {
|
|
6
|
+
label: string;
|
|
7
|
+
value: string;
|
|
8
|
+
};
|
|
9
|
+
type PollOptionResolveArgs<TConfig extends Record<string, unknown> = Record<string, unknown>> = {
|
|
10
|
+
config: TConfig;
|
|
11
|
+
form: {
|
|
12
|
+
id: number | string;
|
|
13
|
+
title?: string;
|
|
14
|
+
};
|
|
15
|
+
payload: Payload;
|
|
16
|
+
req?: PayloadRequest;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* A poll option source type, authored once by the host: `config` is the admin `Field[]` an author
|
|
20
|
+
* fills per form (e.g. an event id); `resolve` maps that config to the current options from host
|
|
21
|
+
* domain data. Called at render time (via `resolvePollOptions`) and again at submission time, where
|
|
22
|
+
* the resolved values are the only accepted answers (fail closed).
|
|
23
|
+
*/
|
|
24
|
+
type PollOptionSource<TConfig extends Record<string, unknown> = Record<string, unknown>> = {
|
|
25
|
+
type: string; /** i18n-key or literal (resolved like a field label), or a per-locale record. */
|
|
26
|
+
label: string | Record<string, string>;
|
|
27
|
+
config?: Field[];
|
|
28
|
+
resolve: (args: PollOptionResolveArgs<TConfig>) => Promise<PollOption[]> | PollOption[];
|
|
29
|
+
/**
|
|
30
|
+
* Optional outcome resolver: return the winning option value(s) from domain data (e.g. the
|
|
31
|
+
* recorded race result), a `string[]` when the result is a tie, or `undefined` while the outcome
|
|
32
|
+
* is not yet decidable. Backs the built-in `source` poll type, so a form whose `poll.type` is
|
|
33
|
+
* `source` auto-resolves through it (via the scheduled close job, a results-read, or an explicit
|
|
34
|
+
* `resolvePollOutcome({ payload, formId })`); every returned value is validated against the poll's
|
|
35
|
+
* resolved options before it is recorded, so each must be one of the values `resolve` yields.
|
|
36
|
+
*/
|
|
37
|
+
resolveOutcome?: (args: PollOptionResolveArgs<TConfig>) => Promise<string | string[] | undefined> | string | string[] | undefined;
|
|
38
|
+
};
|
|
39
|
+
/** Erased shape stored in the registry; config re-narrows per matched type at resolution. */
|
|
40
|
+
type AnyPollOptionSource = PollOptionSource<Record<string, unknown>>;
|
|
41
|
+
declare const definePollOptionSource: <TConfig extends Record<string, unknown> = Record<string, unknown>>(source: PollOptionSource<TConfig>) => PollOptionSource<TConfig>;
|
|
42
|
+
//#endregion
|
|
43
|
+
export { AnyPollOptionSource, PollOption, PollOptionResolveArgs, PollOptionSource, definePollOptionSource };
|
|
44
|
+
//# sourceMappingURL=definePollOptionSource.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"definePollOptionSource.js","names":[],"sources":["../../src/poll/definePollOptionSource.ts"],"sourcesContent":["import type { Field, Payload, PayloadRequest } from 'payload'\n\n/** One selectable poll choice. `value` is the host's stable identifier contract: it is stored in submissions and later matched against `outcome.winningValues`, so it must never change once votes exist. */\nexport type PollOption = { label: string; value: string }\n\nexport type PollOptionResolveArgs<\n\tTConfig extends Record<string, unknown> = Record<string, unknown>,\n> = {\n\tconfig: TConfig\n\tform: { id: number | string; title?: string }\n\tpayload: Payload\n\treq?: PayloadRequest\n}\n\n/**\n * A poll option source type, authored once by the host: `config` is the admin `Field[]` an author\n * fills per form (e.g. an event id); `resolve` maps that config to the current options from host\n * domain data. Called at render time (via `resolvePollOptions`) and again at submission time, where\n * the resolved values are the only accepted answers (fail closed).\n */\nexport type PollOptionSource<TConfig extends Record<string, unknown> = Record<string, unknown>> = {\n\ttype: string\n\t/** i18n-key or literal (resolved like a field label), or a per-locale record. */\n\tlabel: string | Record<string, string>\n\tconfig?: Field[]\n\tresolve: (args: PollOptionResolveArgs<TConfig>) => Promise<PollOption[]> | PollOption[]\n\t/**\n\t * Optional outcome resolver: return the winning option value(s) from domain data (e.g. the\n\t * recorded race result), a `string[]` when the result is a tie, or `undefined` while the outcome\n\t * is not yet decidable. Backs the built-in `source` poll type, so a form whose `poll.type` is\n\t * `source` auto-resolves through it (via the scheduled close job, a results-read, or an explicit\n\t * `resolvePollOutcome({ payload, formId })`); every returned value is validated against the poll's\n\t * resolved options before it is recorded, so each must be one of the values `resolve` yields.\n\t */\n\tresolveOutcome?: (\n\t\targs: PollOptionResolveArgs<TConfig>\n\t) => Promise<string | string[] | undefined> | string | string[] | undefined\n}\n\n/** Erased shape stored in the registry; config re-narrows per matched type at resolution. */\nexport type AnyPollOptionSource = PollOptionSource<Record<string, unknown>>\n\nexport const definePollOptionSource = <\n\tTConfig extends Record<string, unknown> = Record<string, unknown>,\n>(\n\tsource: PollOptionSource<TConfig>\n): PollOptionSource<TConfig> => source\n"],"mappings":";AA0CA,MAAa,0BAGZ,WAC+B"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { FieldAggregation } from "../aggregation/types.js";
|
|
2
|
+
import { PollConfigLike } from "../form/pollState.js";
|
|
3
|
+
import { Field, Payload, PayloadRequest } from "payload";
|
|
4
|
+
|
|
5
|
+
//#region src/poll/definePollType.d.ts
|
|
6
|
+
type PollOutcomeStrategyArgs = {
|
|
7
|
+
payload: Payload;
|
|
8
|
+
req?: PayloadRequest; /** The loaded forms document, widened so a strategy can read host-owned members (e.g. `form.tenant`). */
|
|
9
|
+
form: {
|
|
10
|
+
id: number | string;
|
|
11
|
+
poll?: unknown;
|
|
12
|
+
fields?: unknown;
|
|
13
|
+
} & Record<string, unknown>; /** The poll config narrowed from the form via `pollConfigOf`. */
|
|
14
|
+
poll: PollConfigLike; /** Aggregate the poll's results field, memoized by the caller. Null when there is no results field or no data. */
|
|
15
|
+
aggregate: () => Promise<FieldAggregation | null>;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* An outcome-resolution strategy for a poll, authored once and registered via the plugin `poll.types`
|
|
19
|
+
* option (built-ins `manual`, `mostVoted`, `source` are always registered). The poll's `type` select
|
|
20
|
+
* picks one; `resolvePollOutcome` runs it when no explicit `winningValues` are passed. `resolveOutcome`
|
|
21
|
+
* returns the winning value set, or `undefined` when the outcome is not yet decidable so nothing is
|
|
22
|
+
* written. Every returned value is still validated against the poll's effective options by
|
|
23
|
+
* `pollOutcomeBeforeChange`, so a strategy can never record a value outside the poll's choices.
|
|
24
|
+
*/
|
|
25
|
+
type PollOutcomeStrategy = {
|
|
26
|
+
type: string; /** i18n-key or literal (resolved like a field label), or a per-locale record. */
|
|
27
|
+
label: string | Record<string, string>;
|
|
28
|
+
config?: Field[];
|
|
29
|
+
resolveOutcome: (args: PollOutcomeStrategyArgs) => Promise<string[] | undefined> | string[] | undefined;
|
|
30
|
+
};
|
|
31
|
+
declare const definePollType: (strategy: PollOutcomeStrategy) => PollOutcomeStrategy;
|
|
32
|
+
//#endregion
|
|
33
|
+
export { PollOutcomeStrategy, PollOutcomeStrategyArgs, definePollType };
|
|
34
|
+
//# sourceMappingURL=definePollType.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"definePollType.js","names":[],"sources":["../../src/poll/definePollType.ts"],"sourcesContent":["import type { Field, Payload, PayloadRequest } from 'payload'\nimport type { FieldAggregation } from '../aggregation/types'\nimport type { PollConfigLike } from '../form/pollState'\n\nexport type PollOutcomeStrategyArgs = {\n\tpayload: Payload\n\treq?: PayloadRequest\n\t/** The loaded forms document, widened so a strategy can read host-owned members (e.g. `form.tenant`). */\n\tform: { id: number | string; poll?: unknown; fields?: unknown } & Record<string, unknown>\n\t/** The poll config narrowed from the form via `pollConfigOf`. */\n\tpoll: PollConfigLike\n\t/** Aggregate the poll's results field, memoized by the caller. Null when there is no results field or no data. */\n\taggregate: () => Promise<FieldAggregation | null>\n}\n\n/**\n * An outcome-resolution strategy for a poll, authored once and registered via the plugin `poll.types`\n * option (built-ins `manual`, `mostVoted`, `source` are always registered). The poll's `type` select\n * picks one; `resolvePollOutcome` runs it when no explicit `winningValues` are passed. `resolveOutcome`\n * returns the winning value set, or `undefined` when the outcome is not yet decidable so nothing is\n * written. Every returned value is still validated against the poll's effective options by\n * `pollOutcomeBeforeChange`, so a strategy can never record a value outside the poll's choices.\n */\nexport type PollOutcomeStrategy = {\n\ttype: string\n\t/** i18n-key or literal (resolved like a field label), or a per-locale record. */\n\tlabel: string | Record<string, string>\n\tconfig?: Field[]\n\tresolveOutcome: (\n\t\targs: PollOutcomeStrategyArgs\n\t) => Promise<string[] | undefined> | string[] | undefined\n}\n\nexport const definePollType = (strategy: PollOutcomeStrategy): PollOutcomeStrategy => strategy\n"],"mappings":";AAiCA,MAAa,kBAAkB,aAAuD"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { PollOption } from "./definePollOptionSource.js";
|
|
2
|
+
import { FieldTypeRegistry } from "../fields/registry.js";
|
|
3
|
+
import { PollOptionSourceRegistry } from "./registry.js";
|
|
4
|
+
import { Payload, PayloadRequest } from "payload";
|
|
5
|
+
|
|
6
|
+
//#region src/poll/effectivePollOptions.d.ts
|
|
7
|
+
type ResolveEffectivePollOptionsArgs = {
|
|
8
|
+
payload: Payload;
|
|
9
|
+
req?: PayloadRequest; /** A forms document (or an equivalent merged view of one during a save). */
|
|
10
|
+
form: {
|
|
11
|
+
id: number | string;
|
|
12
|
+
title?: string | null; /** Top-level poll-enable flag; the poll config (results field, source) stays under `poll`. */
|
|
13
|
+
pollEnabled?: boolean | null;
|
|
14
|
+
poll?: unknown;
|
|
15
|
+
fields?: unknown;
|
|
16
|
+
}; /** Registry override for plugin-internal callers; defaults to the registry stashed on the config. */
|
|
17
|
+
sources?: PollOptionSourceRegistry;
|
|
18
|
+
/**
|
|
19
|
+
* Field-type registry override; defaults to the one stashed on the config. Lets the results
|
|
20
|
+
* field's definition source its own options via {@link FormFieldDefinition.resolveOptions}.
|
|
21
|
+
*/
|
|
22
|
+
fieldTypes?: FieldTypeRegistry;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* The poll's currently valid choice set, resolved in a fixed precedence: source-resolved options
|
|
26
|
+
* when the poll has an `optionSource`, else the results field type's own {@link
|
|
27
|
+
* FormFieldDefinition.resolveOptions} when it declares one (given the live field instance so a
|
|
28
|
+
* custom type can enumerate the values it holds), else the authored options of the field instance.
|
|
29
|
+
* Empty when the poll is disabled, no results field is set, or none of the branches yields options.
|
|
30
|
+
* This single set backs both the admin winner select (the `/:id/poll-options` endpoint) and outcome
|
|
31
|
+
* membership validation, so what the admin offers and what the server accepts cannot drift. Source
|
|
32
|
+
* and resolver failures propagate so callers fail closed; the resolver branch is cached per request
|
|
33
|
+
* (keyed by form id) when a `req` is provided, matching the source path.
|
|
34
|
+
*/
|
|
35
|
+
declare const resolveEffectivePollOptions: (args: ResolveEffectivePollOptionsArgs) => Promise<PollOption[]>;
|
|
36
|
+
//#endregion
|
|
37
|
+
export { ResolveEffectivePollOptionsArgs, resolveEffectivePollOptions };
|
|
38
|
+
//# sourceMappingURL=effectivePollOptions.d.ts.map
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { fieldTypesOf } from "../fields/registry.js";
|
|
2
|
+
import { pollConfigOf } from "../form/pollState.js";
|
|
3
|
+
import { instanceOptionsOf } from "../fields/instanceOptions.js";
|
|
4
|
+
import { resolvePollOptions } from "./resolvePollOptions.js";
|
|
5
|
+
//#region src/poll/effectivePollOptions.ts
|
|
6
|
+
const CACHE_KEY = "formBuilderFieldOptionsCache";
|
|
7
|
+
const cacheOf = (req) => {
|
|
8
|
+
if (!req?.context) return;
|
|
9
|
+
const existing = req.context[CACHE_KEY];
|
|
10
|
+
if (existing instanceof Map) return existing;
|
|
11
|
+
const created = /* @__PURE__ */ new Map();
|
|
12
|
+
req.context[CACHE_KEY] = created;
|
|
13
|
+
return created;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* The poll's currently valid choice set, resolved in a fixed precedence: source-resolved options
|
|
17
|
+
* when the poll has an `optionSource`, else the results field type's own {@link
|
|
18
|
+
* FormFieldDefinition.resolveOptions} when it declares one (given the live field instance so a
|
|
19
|
+
* custom type can enumerate the values it holds), else the authored options of the field instance.
|
|
20
|
+
* Empty when the poll is disabled, no results field is set, or none of the branches yields options.
|
|
21
|
+
* This single set backs both the admin winner select (the `/:id/poll-options` endpoint) and outcome
|
|
22
|
+
* membership validation, so what the admin offers and what the server accepts cannot drift. Source
|
|
23
|
+
* and resolver failures propagate so callers fail closed; the resolver branch is cached per request
|
|
24
|
+
* (keyed by form id) when a `req` is provided, matching the source path.
|
|
25
|
+
*/
|
|
26
|
+
const resolveEffectivePollOptions = async (args) => {
|
|
27
|
+
const { payload, req, form, sources, fieldTypes } = args;
|
|
28
|
+
const poll = pollConfigOf(form.poll);
|
|
29
|
+
if (form.pollEnabled !== true || !poll) return [];
|
|
30
|
+
const sourced = await resolvePollOptions({
|
|
31
|
+
payload,
|
|
32
|
+
req,
|
|
33
|
+
form,
|
|
34
|
+
sources
|
|
35
|
+
});
|
|
36
|
+
if (sourced) return sourced;
|
|
37
|
+
const resultsField = typeof poll.resultsField === "string" && poll.resultsField.length > 0 ? poll.resultsField : void 0;
|
|
38
|
+
if (!resultsField) return [];
|
|
39
|
+
const instance = (Array.isArray(form.fields) ? form.fields : []).find((entry) => entry.name === resultsField);
|
|
40
|
+
if (!instance) return [];
|
|
41
|
+
const definition = (fieldTypes ?? fieldTypesOf(payload)).get(instance.blockType);
|
|
42
|
+
if (definition?.resolveOptions) {
|
|
43
|
+
const cache = cacheOf(req);
|
|
44
|
+
const cacheKey = String(form.id);
|
|
45
|
+
const cached = cache?.get(cacheKey);
|
|
46
|
+
if (cached) return cached;
|
|
47
|
+
const options = await definition.resolveOptions({
|
|
48
|
+
instance,
|
|
49
|
+
form: {
|
|
50
|
+
id: form.id,
|
|
51
|
+
title: typeof form.title === "string" ? form.title : void 0
|
|
52
|
+
},
|
|
53
|
+
payload,
|
|
54
|
+
req
|
|
55
|
+
});
|
|
56
|
+
cache?.set(cacheKey, options);
|
|
57
|
+
return options;
|
|
58
|
+
}
|
|
59
|
+
return instanceOptionsOf(instance) ?? [];
|
|
60
|
+
};
|
|
61
|
+
//#endregion
|
|
62
|
+
export { resolveEffectivePollOptions };
|
|
63
|
+
|
|
64
|
+
//# sourceMappingURL=effectivePollOptions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"effectivePollOptions.js","names":[],"sources":["../../src/poll/effectivePollOptions.ts"],"sourcesContent":["import type { Payload, PayloadRequest } from 'payload'\nimport { instanceOptionsOf } from '../fields/instanceOptions'\nimport { type FieldTypeRegistry, fieldTypesOf } from '../fields/registry'\nimport { pollConfigOf } from '../form/pollState'\nimport type { FormFieldInstance } from '../submissions/types'\nimport type { PollOption } from './definePollOptionSource'\nimport type { PollOptionSourceRegistry } from './registry'\nimport { resolvePollOptions } from './resolvePollOptions'\n\nexport type ResolveEffectivePollOptionsArgs = {\n\tpayload: Payload\n\treq?: PayloadRequest\n\t/** A forms document (or an equivalent merged view of one during a save). */\n\tform: {\n\t\tid: number | string\n\t\ttitle?: string | null\n\t\t/** Top-level poll-enable flag; the poll config (results field, source) stays under `poll`. */\n\t\tpollEnabled?: boolean | null\n\t\tpoll?: unknown\n\t\tfields?: unknown\n\t}\n\t/** Registry override for plugin-internal callers; defaults to the registry stashed on the config. */\n\tsources?: PollOptionSourceRegistry\n\t/**\n\t * Field-type registry override; defaults to the one stashed on the config. Lets the results\n\t * field's definition source its own options via {@link FormFieldDefinition.resolveOptions}.\n\t */\n\tfieldTypes?: FieldTypeRegistry\n}\n\nconst CACHE_KEY = 'formBuilderFieldOptionsCache'\n\ntype OptionsCache = Map<string, PollOption[]>\n\nconst cacheOf = (req: PayloadRequest | undefined): OptionsCache | undefined => {\n\tif (!req?.context) {\n\t\treturn undefined\n\t}\n\tconst existing = req.context[CACHE_KEY]\n\tif (existing instanceof Map) {\n\t\treturn existing as OptionsCache\n\t}\n\tconst created: OptionsCache = new Map()\n\treq.context[CACHE_KEY] = created\n\treturn created\n}\n\n/**\n * The poll's currently valid choice set, resolved in a fixed precedence: source-resolved options\n * when the poll has an `optionSource`, else the results field type's own {@link\n * FormFieldDefinition.resolveOptions} when it declares one (given the live field instance so a\n * custom type can enumerate the values it holds), else the authored options of the field instance.\n * Empty when the poll is disabled, no results field is set, or none of the branches yields options.\n * This single set backs both the admin winner select (the `/:id/poll-options` endpoint) and outcome\n * membership validation, so what the admin offers and what the server accepts cannot drift. Source\n * and resolver failures propagate so callers fail closed; the resolver branch is cached per request\n * (keyed by form id) when a `req` is provided, matching the source path.\n */\nexport const resolveEffectivePollOptions = async (\n\targs: ResolveEffectivePollOptionsArgs\n): Promise<PollOption[]> => {\n\tconst { payload, req, form, sources, fieldTypes } = args\n\tconst poll = pollConfigOf(form.poll)\n\tif (form.pollEnabled !== true || !poll) {\n\t\treturn []\n\t}\n\tconst sourced = await resolvePollOptions({ payload, req, form, sources })\n\tif (sourced) {\n\t\treturn sourced\n\t}\n\tconst resultsField =\n\t\ttypeof poll.resultsField === 'string' && poll.resultsField.length > 0\n\t\t\t? poll.resultsField\n\t\t\t: undefined\n\tif (!resultsField) {\n\t\treturn []\n\t}\n\tconst instances = Array.isArray(form.fields) ? (form.fields as FormFieldInstance[]) : []\n\tconst instance = instances.find((entry) => entry.name === resultsField)\n\tif (!instance) {\n\t\treturn []\n\t}\n\tconst definition = (fieldTypes ?? fieldTypesOf(payload)).get(instance.blockType)\n\tif (definition?.resolveOptions) {\n\t\tconst cache = cacheOf(req)\n\t\tconst cacheKey = String(form.id)\n\t\tconst cached = cache?.get(cacheKey)\n\t\tif (cached) {\n\t\t\treturn cached\n\t\t}\n\t\tconst options = await definition.resolveOptions({\n\t\t\tinstance,\n\t\t\tform: { id: form.id, title: typeof form.title === 'string' ? form.title : undefined },\n\t\t\tpayload,\n\t\t\treq,\n\t\t})\n\t\tcache?.set(cacheKey, options)\n\t\treturn options\n\t}\n\treturn instanceOptionsOf(instance) ?? []\n}\n"],"mappings":";;;;;AA8BA,MAAM,YAAY;AAIlB,MAAM,WAAW,QAA8D;CAC9E,IAAI,CAAC,KAAK,SACT;CAED,MAAM,WAAW,IAAI,QAAQ;CAC7B,IAAI,oBAAoB,KACvB,OAAO;CAER,MAAM,0BAAwB,IAAI,IAAI;CACtC,IAAI,QAAQ,aAAa;CACzB,OAAO;AACR;;;;;;;;;;;;AAaA,MAAa,8BAA8B,OAC1C,SAC2B;CAC3B,MAAM,EAAE,SAAS,KAAK,MAAM,SAAS,eAAe;CACpD,MAAM,OAAO,aAAa,KAAK,IAAI;CACnC,IAAI,KAAK,gBAAgB,QAAQ,CAAC,MACjC,OAAO,CAAC;CAET,MAAM,UAAU,MAAM,mBAAmB;EAAE;EAAS;EAAK;EAAM;CAAQ,CAAC;CACxE,IAAI,SACH,OAAO;CAER,MAAM,eACL,OAAO,KAAK,iBAAiB,YAAY,KAAK,aAAa,SAAS,IACjE,KAAK,eACL,KAAA;CACJ,IAAI,CAAC,cACJ,OAAO,CAAC;CAGT,MAAM,YADY,MAAM,QAAQ,KAAK,MAAM,IAAK,KAAK,SAAiC,CAAC,GAC5D,MAAM,UAAU,MAAM,SAAS,YAAY;CACtE,IAAI,CAAC,UACJ,OAAO,CAAC;CAET,MAAM,cAAc,cAAc,aAAa,OAAO,GAAG,IAAI,SAAS,SAAS;CAC/E,IAAI,YAAY,gBAAgB;EAC/B,MAAM,QAAQ,QAAQ,GAAG;EACzB,MAAM,WAAW,OAAO,KAAK,EAAE;EAC/B,MAAM,SAAS,OAAO,IAAI,QAAQ;EAClC,IAAI,QACH,OAAO;EAER,MAAM,UAAU,MAAM,WAAW,eAAe;GAC/C;GACA,MAAM;IAAE,IAAI,KAAK;IAAI,OAAO,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ,KAAA;GAAU;GACpF;GACA;EACD,CAAC;EACD,OAAO,IAAI,UAAU,OAAO;EAC5B,OAAO;CACR;CACA,OAAO,kBAAkB,QAAQ,KAAK,CAAC;AACxC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { PollOutcomeStrategy } from "./definePollType.js";
|
|
2
|
+
|
|
3
|
+
//#region src/poll/mostVoted.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Every bucket value tied for the highest count, so a draw records all co-winners. Empty when there
|
|
6
|
+
* are no buckets. Pure: the caller decides whether the aggregation is trustworthy enough to act on.
|
|
7
|
+
*/
|
|
8
|
+
declare const topBucketValues: (buckets: {
|
|
9
|
+
value: string;
|
|
10
|
+
count: number;
|
|
11
|
+
}[]) => string[];
|
|
12
|
+
/**
|
|
13
|
+
* Auto-resolve to whichever choice(s) received the most votes. Refuses to decide, returning
|
|
14
|
+
* `undefined` (no write), when there is no aggregation, no responses yet, or the aggregation was
|
|
15
|
+
* `truncated` (a sample cannot be trusted to name the true winner, so it is flagged for a manual
|
|
16
|
+
* call instead). A perfect tie records every co-winner.
|
|
17
|
+
*/
|
|
18
|
+
declare const mostVotedStrategy: PollOutcomeStrategy;
|
|
19
|
+
//#endregion
|
|
20
|
+
export { mostVotedStrategy, topBucketValues };
|
|
21
|
+
//# sourceMappingURL=mostVoted.d.ts.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { keys } from "../translations/keys.js";
|
|
2
|
+
import { definePollType } from "./definePollType.js";
|
|
3
|
+
//#region src/poll/mostVoted.ts
|
|
4
|
+
/**
|
|
5
|
+
* Every bucket value tied for the highest count, so a draw records all co-winners. Empty when there
|
|
6
|
+
* are no buckets. Pure: the caller decides whether the aggregation is trustworthy enough to act on.
|
|
7
|
+
*/
|
|
8
|
+
const topBucketValues = (buckets) => {
|
|
9
|
+
if (buckets.length === 0) return [];
|
|
10
|
+
const max = Math.max(...buckets.map((bucket) => bucket.count));
|
|
11
|
+
return buckets.filter((bucket) => bucket.count === max).map((bucket) => bucket.value);
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Auto-resolve to whichever choice(s) received the most votes. Refuses to decide, returning
|
|
15
|
+
* `undefined` (no write), when there is no aggregation, no responses yet, or the aggregation was
|
|
16
|
+
* `truncated` (a sample cannot be trusted to name the true winner, so it is flagged for a manual
|
|
17
|
+
* call instead). A perfect tie records every co-winner.
|
|
18
|
+
*/
|
|
19
|
+
const mostVotedStrategy = definePollType({
|
|
20
|
+
type: "mostVoted",
|
|
21
|
+
label: keys.pollTypeMostVoted,
|
|
22
|
+
resolveOutcome: async ({ aggregate }) => {
|
|
23
|
+
const aggregation = await aggregate();
|
|
24
|
+
if (!aggregation || aggregation.total === 0 || aggregation.truncated) return;
|
|
25
|
+
const winners = topBucketValues(aggregation.buckets);
|
|
26
|
+
return winners.length > 0 ? winners : void 0;
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
//#endregion
|
|
30
|
+
export { mostVotedStrategy, topBucketValues };
|
|
31
|
+
|
|
32
|
+
//# sourceMappingURL=mostVoted.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mostVoted.js","names":[],"sources":["../../src/poll/mostVoted.ts"],"sourcesContent":["import { keys } from '../translations/keys'\nimport { definePollType, type PollOutcomeStrategy } from './definePollType'\n\n/**\n * Every bucket value tied for the highest count, so a draw records all co-winners. Empty when there\n * are no buckets. Pure: the caller decides whether the aggregation is trustworthy enough to act on.\n */\nexport const topBucketValues = (buckets: { value: string; count: number }[]): string[] => {\n\tif (buckets.length === 0) {\n\t\treturn []\n\t}\n\tconst max = Math.max(...buckets.map((bucket) => bucket.count))\n\treturn buckets.filter((bucket) => bucket.count === max).map((bucket) => bucket.value)\n}\n\n/**\n * Auto-resolve to whichever choice(s) received the most votes. Refuses to decide, returning\n * `undefined` (no write), when there is no aggregation, no responses yet, or the aggregation was\n * `truncated` (a sample cannot be trusted to name the true winner, so it is flagged for a manual\n * call instead). A perfect tie records every co-winner.\n */\nexport const mostVotedStrategy: PollOutcomeStrategy = definePollType({\n\ttype: 'mostVoted',\n\tlabel: keys.pollTypeMostVoted,\n\tresolveOutcome: async ({ aggregate }) => {\n\t\tconst aggregation = await aggregate()\n\t\tif (!aggregation || aggregation.total === 0 || aggregation.truncated) {\n\t\t\treturn undefined\n\t\t}\n\t\tconst winners = topBucketValues(aggregation.buckets)\n\t\treturn winners.length > 0 ? winners : undefined\n\t},\n})\n"],"mappings":";;;;;;;AAOA,MAAa,mBAAmB,YAA0D;CACzF,IAAI,QAAQ,WAAW,GACtB,OAAO,CAAC;CAET,MAAM,MAAM,KAAK,IAAI,GAAG,QAAQ,KAAK,WAAW,OAAO,KAAK,CAAC;CAC7D,OAAO,QAAQ,QAAQ,WAAW,OAAO,UAAU,GAAG,EAAE,KAAK,WAAW,OAAO,KAAK;AACrF;;;;;;;AAQA,MAAa,oBAAyC,eAAe;CACpE,MAAM;CACN,OAAO,KAAK;CACZ,gBAAgB,OAAO,EAAE,gBAAgB;EACxC,MAAM,cAAc,MAAM,UAAU;EACpC,IAAI,CAAC,eAAe,YAAY,UAAU,KAAK,YAAY,WAC1D;EAED,MAAM,UAAU,gBAAgB,YAAY,OAAO;EACnD,OAAO,QAAQ,SAAS,IAAI,UAAU,KAAA;CACvC;AACD,CAAC"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { keys } from "../translations/keys.js";
|
|
2
|
+
import { asTranslate } from "../translations/server.js";
|
|
3
|
+
import { pollConfigOf } from "../form/pollState.js";
|
|
4
|
+
import { resolveEffectivePollOptions } from "./effectivePollOptions.js";
|
|
5
|
+
import { FORMS_SLUG } from "../collections/forms.js";
|
|
6
|
+
import { ValidationError } from "payload";
|
|
7
|
+
//#region src/poll/outcomeBeforeChange.ts
|
|
8
|
+
const WINNING_VALUES_PATH = "poll.outcome.winningValues";
|
|
9
|
+
/** The winning set as a deduped array of non-empty strings, ready to store and to compare. */
|
|
10
|
+
const normalizeWinning = (value) => {
|
|
11
|
+
if (!Array.isArray(value)) return [];
|
|
12
|
+
const seen = /* @__PURE__ */ new Set();
|
|
13
|
+
for (const entry of value) if (typeof entry === "string" && entry.length > 0) seen.add(entry);
|
|
14
|
+
return [...seen];
|
|
15
|
+
};
|
|
16
|
+
/** Set equality on two already-deduped winner arrays: order-insensitive membership. */
|
|
17
|
+
const sameWinners = (a, b) => {
|
|
18
|
+
if (a.length !== b.length) return false;
|
|
19
|
+
const set = new Set(a);
|
|
20
|
+
return b.every((value) => set.has(value));
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Owns the poll outcome lifecycle for every create and update, admin and API alike. When the save
|
|
24
|
+
* writes `poll.outcome.winningValues` it validates a set or changed winner set against the poll's
|
|
25
|
+
* effective options (source-resolved or authored, over a merged view of the doc so partial updates
|
|
26
|
+
* validate against stored config) and requires the poll to be enabled; clearing is always allowed.
|
|
27
|
+
* A tie is a set with more than one winner; every value must be a member of the effective options.
|
|
28
|
+
*
|
|
29
|
+
* It is also the single `resolvedAt` stamping point: stamped when the winner set is set or changed,
|
|
30
|
+
* cleared with the set, untouched when the set is unchanged (order-insensitive). Callers can never
|
|
31
|
+
* write `resolvedAt` themselves (field-level access strips it during the beforeValidate traversal,
|
|
32
|
+
* before this hook runs), while values this hook sets survive to the database, so the stamp always
|
|
33
|
+
* reflects the actual transition regardless of the write path.
|
|
34
|
+
*/
|
|
35
|
+
const pollOutcomeBeforeChange = async ({ data, originalDoc, req }) => {
|
|
36
|
+
const incomingPoll = data?.poll != null && typeof data.poll === "object" ? data.poll : void 0;
|
|
37
|
+
const outcome = incomingPoll?.outcome != null && typeof incomingPoll.outcome === "object" ? incomingPoll.outcome : void 0;
|
|
38
|
+
if (!outcome || !("winningValues" in outcome)) return data;
|
|
39
|
+
const next = normalizeWinning(outcome.winningValues);
|
|
40
|
+
outcome.winningValues = next;
|
|
41
|
+
if (sameWinners(next, normalizeWinning(pollConfigOf(originalDoc?.poll)?.outcome?.winningValues))) return data;
|
|
42
|
+
if (next.length === 0) {
|
|
43
|
+
outcome.resolvedAt = null;
|
|
44
|
+
return data;
|
|
45
|
+
}
|
|
46
|
+
const t = asTranslate(req.t);
|
|
47
|
+
const fail = (message) => {
|
|
48
|
+
throw new ValidationError({
|
|
49
|
+
collection: FORMS_SLUG,
|
|
50
|
+
errors: [{
|
|
51
|
+
path: WINNING_VALUES_PATH,
|
|
52
|
+
message
|
|
53
|
+
}]
|
|
54
|
+
}, req.t);
|
|
55
|
+
};
|
|
56
|
+
const mergedPoll = {
|
|
57
|
+
...(originalDoc?.poll != null && typeof originalDoc.poll === "object" ? originalDoc.poll : void 0) ?? {},
|
|
58
|
+
...incomingPoll
|
|
59
|
+
};
|
|
60
|
+
const mergedPollEnabled = {
|
|
61
|
+
...originalDoc ?? {},
|
|
62
|
+
...data
|
|
63
|
+
}.pollEnabled === true;
|
|
64
|
+
if (!mergedPollEnabled) fail(t(keys.validationWinningValueDisabled));
|
|
65
|
+
const id = originalDoc?.id ?? data?.id;
|
|
66
|
+
const form = {
|
|
67
|
+
id: id ?? "",
|
|
68
|
+
title: typeof data?.title === "string" ? data.title : originalDoc?.title,
|
|
69
|
+
pollEnabled: mergedPollEnabled,
|
|
70
|
+
poll: mergedPoll,
|
|
71
|
+
fields: data?.fields ?? originalDoc?.fields
|
|
72
|
+
};
|
|
73
|
+
let options;
|
|
74
|
+
try {
|
|
75
|
+
options = await resolveEffectivePollOptions({
|
|
76
|
+
payload: req.payload,
|
|
77
|
+
req: id != null ? req : void 0,
|
|
78
|
+
form
|
|
79
|
+
});
|
|
80
|
+
} catch {
|
|
81
|
+
return fail(t(keys.pollOptionsUnavailable));
|
|
82
|
+
}
|
|
83
|
+
const allowed = new Set(options.map((option) => option.value));
|
|
84
|
+
if (!next.every((value) => allowed.has(value))) fail(t(keys.validationWinningValueUnknown));
|
|
85
|
+
outcome.resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
86
|
+
return data;
|
|
87
|
+
};
|
|
88
|
+
//#endregion
|
|
89
|
+
export { pollOutcomeBeforeChange };
|
|
90
|
+
|
|
91
|
+
//# sourceMappingURL=outcomeBeforeChange.js.map
|