@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,37 @@
|
|
|
1
|
+
import { RecipientResolveArgs } from "./recipientSources.js";
|
|
2
|
+
import { PayloadRequest } from "payload";
|
|
3
|
+
|
|
4
|
+
//#region src/actions/fromAddresses.d.ts
|
|
5
|
+
/** One selectable "from" address for the built-in email actions. */
|
|
6
|
+
type FromAddressOption = {
|
|
7
|
+
label: string;
|
|
8
|
+
value: string;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* A sender the plugin resolves server-side at send time (plugin option `email.fromSources`), the
|
|
12
|
+
* from-side counterpart of a `RecipientSource`. `value` is the namespaced string stored on the
|
|
13
|
+
* action (e.g. `tenant:default`), so it cannot collide with a literal address and stays
|
|
14
|
+
* audit-stable while the address it resolves to follows the host; `label` is what the editor sees
|
|
15
|
+
* in the from select. `resolve` returns the address to send from right now (reduced to a single
|
|
16
|
+
* address), or null/empty to send with the email adapter's default sender. A throw fails the
|
|
17
|
+
* action loudly (and retries on the queued path) rather than sending as the wrong identity.
|
|
18
|
+
*/
|
|
19
|
+
type FromAddressSource = {
|
|
20
|
+
value: string;
|
|
21
|
+
label: string | Record<string, string>;
|
|
22
|
+
resolve: (args: RecipientResolveArgs) => Promise<string | null> | string | null;
|
|
23
|
+
};
|
|
24
|
+
type FromAddressSourceRegistry = Record<string, FromAddressSource>;
|
|
25
|
+
/**
|
|
26
|
+
* Host seam resolving the selectable `from` addresses for `emailTeam`/`confirmation`
|
|
27
|
+
* (plugin option `email.fromAddresses`). Multi-tenant hosts derive tenant scoping from `req`
|
|
28
|
+
* (host header, cookie, or auth context) and return only that tenant's allowed senders. `value`
|
|
29
|
+
* is the literal string handed to `payload.sendEmail`'s `from` (e.g. `'Name <addr@x.com>'` or a
|
|
30
|
+
* plain address). Absent keeps the email adapter's default sender and adds no `from` field at all.
|
|
31
|
+
*/
|
|
32
|
+
type FromAddressesResolver = (args: {
|
|
33
|
+
req: PayloadRequest;
|
|
34
|
+
}) => Promise<FromAddressOption[]> | FromAddressOption[];
|
|
35
|
+
//#endregion
|
|
36
|
+
export { FromAddressOption, FromAddressSource, FromAddressSourceRegistry, FromAddressesResolver };
|
|
37
|
+
//# sourceMappingURL=fromAddresses.d.ts.map
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { keys } from "../translations/keys.js";
|
|
2
|
+
import { asTranslate, labelFor } from "../translations/server.js";
|
|
3
|
+
import { isPlausibleEmail } from "./emailRecipients.js";
|
|
4
|
+
//#region src/actions/fromAddresses.ts
|
|
5
|
+
/**
|
|
6
|
+
* The `from` handed to `payload.sendEmail`: a stored source value re-resolves through its source
|
|
7
|
+
* with the run-time args; anything else (a literal picked from `fromAddresses`, which never
|
|
8
|
+
* touches a source at send) is forwarded verbatim, and no configured value means no `from` at
|
|
9
|
+
* all. Mirrors `resolveRecipientEntries`: a throwing source propagates.
|
|
10
|
+
*/
|
|
11
|
+
const resolveSendFrom = async (opts) => {
|
|
12
|
+
const { configured, sources, sourceArgs } = opts;
|
|
13
|
+
if (!configured) return;
|
|
14
|
+
const source = sources?.get(configured);
|
|
15
|
+
if (!source) return configured;
|
|
16
|
+
const resolved = sourceArgs ? await source.resolve(sourceArgs) : null;
|
|
17
|
+
if (!resolved) return;
|
|
18
|
+
return firstSender(resolved) || void 0;
|
|
19
|
+
};
|
|
20
|
+
/** A bare plausible address, or a `Name <addr>` display form wrapping one (quotes and commas in the name included). */
|
|
21
|
+
const isPlausibleSender = (value) => {
|
|
22
|
+
if (isPlausibleEmail(value)) return true;
|
|
23
|
+
const bracketed = /^[^<>]*<([^<>\s]+)>$/.exec(value);
|
|
24
|
+
return Boolean(bracketed?.[1] && isPlausibleEmail(bracketed[1]));
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* The sender-side counterpart of `firstAddress`: one sender only, but `Name <addr>` display form
|
|
28
|
+
* survives because that is the documented shape of a `from`. Order matters: cut at line breaks
|
|
29
|
+
* first (the header-injection vector), accept the whole remaining line so a quoted display name
|
|
30
|
+
* may contain commas, and only then comma-split to clamp a multi-address result to its first
|
|
31
|
+
* entry. An implausible result becomes empty (send with the adapter default) rather than a
|
|
32
|
+
* broken header.
|
|
33
|
+
*/
|
|
34
|
+
const firstSender = (value) => {
|
|
35
|
+
const line = (value.split(/[\n\r]+/)[0] ?? "").trim();
|
|
36
|
+
if (isPlausibleSender(line)) return line;
|
|
37
|
+
const [first] = line.split(/[,;]+/);
|
|
38
|
+
const cleaned = (first ?? "").trim();
|
|
39
|
+
return isPlausibleSender(cleaned) ? cleaned : "";
|
|
40
|
+
};
|
|
41
|
+
const FROM_FIELD_REF = "@10x-media/form-builder/client#EndpointOptionsSelect";
|
|
42
|
+
/**
|
|
43
|
+
* Validate for the `from` field, closed over the host resolver (mirrors the confirmation action's
|
|
44
|
+
* `toField` and poll's `resultsField`): unset is fine, otherwise the value must be one of the
|
|
45
|
+
* resolver's options for this request. A throwing resolver fails closed with a translated message
|
|
46
|
+
* rather than surfacing a raw error on save.
|
|
47
|
+
*
|
|
48
|
+
* Failing closed has an operational cost worth knowing: Payload runs this on every save, not only
|
|
49
|
+
* when `from` changed, so for as long as the resolver is down no form carrying an email action with
|
|
50
|
+
* a `from` set can be saved at all, including edits that never touch the address. That is the
|
|
51
|
+
* deliberate trade: failing open would persist a sender the host can no longer vouch for, and
|
|
52
|
+
* unlike `toField` and `resultsField` this seam depends on host infrastructure that can be down.
|
|
53
|
+
* A resolver reaching a flaky upstream should cache or fall back internally rather than throw.
|
|
54
|
+
*/
|
|
55
|
+
const validateFromField = (resolver, sourceValues) => async (value, { req }) => {
|
|
56
|
+
if (typeof value !== "string" || value.length === 0) return true;
|
|
57
|
+
if (sourceValues?.has(value)) return true;
|
|
58
|
+
if (!resolver) return asTranslate(req.t)(keys.validationFromUnknown);
|
|
59
|
+
let options;
|
|
60
|
+
try {
|
|
61
|
+
options = await resolver({ req });
|
|
62
|
+
} catch {
|
|
63
|
+
return asTranslate(req.t)(keys.validationFromUnavailable);
|
|
64
|
+
}
|
|
65
|
+
return options.some((option) => option.value === value) ? true : asTranslate(req.t)(keys.validationFromUnknown);
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* The `from` select shared by `emailTeam` and `confirmation`: an `EndpointOptionsSelect` backed by
|
|
69
|
+
* the forms collection's `/from-addresses` endpoint (registered when `email.fromAddresses` or
|
|
70
|
+
* `email.fromSources` is set). The option set is request-scoped, not per-form, so the select is
|
|
71
|
+
* marked `scope: 'request'` and its options load while the form is still being created.
|
|
72
|
+
*/
|
|
73
|
+
const buildFromField = (resolver, sources) => ({
|
|
74
|
+
name: "from",
|
|
75
|
+
type: "text",
|
|
76
|
+
label: labelFor(keys.actionConfigFrom),
|
|
77
|
+
validate: validateFromField(resolver, sources ? new Set(Object.values(sources).map((source) => source.value)) : void 0),
|
|
78
|
+
admin: { components: { Field: {
|
|
79
|
+
path: FROM_FIELD_REF,
|
|
80
|
+
clientProps: {
|
|
81
|
+
endpoint: "from-addresses",
|
|
82
|
+
scope: "request",
|
|
83
|
+
descriptionKey: keys.actionConfigFromDescription
|
|
84
|
+
}
|
|
85
|
+
} } }
|
|
86
|
+
});
|
|
87
|
+
/**
|
|
88
|
+
* A source entry as the from select shows it. A string label is display text served raw (matching
|
|
89
|
+
* how `RecipientsSelect` receives source labels); a per-locale record picks the request's admin
|
|
90
|
+
* language, then English, then any value.
|
|
91
|
+
*/
|
|
92
|
+
const sourceOption = (source, req) => {
|
|
93
|
+
if (typeof source.label === "string") return {
|
|
94
|
+
label: source.label,
|
|
95
|
+
value: source.value
|
|
96
|
+
};
|
|
97
|
+
return {
|
|
98
|
+
label: source.label[req.i18n.language] ?? source.label.en ?? Object.values(source.label)[0] ?? source.value,
|
|
99
|
+
value: source.value
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* Authorize and resolve the `GET /:id/from-addresses` request backing the `from` selects:
|
|
104
|
+
* authenticated callers get the host resolver's current options for this request; anonymous
|
|
105
|
+
* callers are always refused. The route id is unused (see `buildFromField`). Statuses mirror
|
|
106
|
+
* the poll-options endpoint: 403 unauthenticated, 503 when the resolver throws (fail closed).
|
|
107
|
+
*/
|
|
108
|
+
const resolveFromAddressesRequest = async (args) => {
|
|
109
|
+
const { isAuthed, req, resolver, sources } = args;
|
|
110
|
+
if (!isAuthed) return {
|
|
111
|
+
status: 403,
|
|
112
|
+
body: { errors: [{ message: "Forbidden" }] }
|
|
113
|
+
};
|
|
114
|
+
try {
|
|
115
|
+
const sourceOptions = Object.values(sources ?? {}).map((source) => sourceOption(source, req));
|
|
116
|
+
const resolved = resolver ? await resolver({ req }) : [];
|
|
117
|
+
return {
|
|
118
|
+
status: 200,
|
|
119
|
+
body: { options: [...sourceOptions, ...resolved] }
|
|
120
|
+
};
|
|
121
|
+
} catch {
|
|
122
|
+
return {
|
|
123
|
+
status: 503,
|
|
124
|
+
body: { errors: [{ message: "From addresses unavailable" }] }
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
//#endregion
|
|
129
|
+
export { buildFromField, resolveFromAddressesRequest, resolveSendFrom };
|
|
130
|
+
|
|
131
|
+
//# sourceMappingURL=fromAddresses.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fromAddresses.js","names":[],"sources":["../../src/actions/fromAddresses.ts"],"sourcesContent":["import type { PayloadRequest, TextField } from 'payload'\nimport { keys } from '../translations/keys'\nimport { asTranslate, labelFor } from '../translations/server'\nimport { isPlausibleEmail } from './emailRecipients'\nimport type { RecipientResolveArgs } from './recipientSources'\n\n/** One selectable \"from\" address for the built-in email actions. */\nexport type FromAddressOption = { label: string; value: string }\n\n/**\n * A sender the plugin resolves server-side at send time (plugin option `email.fromSources`), the\n * from-side counterpart of a `RecipientSource`. `value` is the namespaced string stored on the\n * action (e.g. `tenant:default`), so it cannot collide with a literal address and stays\n * audit-stable while the address it resolves to follows the host; `label` is what the editor sees\n * in the from select. `resolve` returns the address to send from right now (reduced to a single\n * address), or null/empty to send with the email adapter's default sender. A throw fails the\n * action loudly (and retries on the queued path) rather than sending as the wrong identity.\n */\nexport type FromAddressSource = {\n\tvalue: string\n\tlabel: string | Record<string, string>\n\tresolve: (args: RecipientResolveArgs) => Promise<string | null> | string | null\n}\n\nexport type FromAddressSourceRegistry = Record<string, FromAddressSource>\n\n/**\n * The `from` handed to `payload.sendEmail`: a stored source value re-resolves through its source\n * with the run-time args; anything else (a literal picked from `fromAddresses`, which never\n * touches a source at send) is forwarded verbatim, and no configured value means no `from` at\n * all. Mirrors `resolveRecipientEntries`: a throwing source propagates.\n */\nexport const resolveSendFrom = async (opts: {\n\tconfigured: string | undefined\n\tsources?: Map<string, FromAddressSource>\n\tsourceArgs?: RecipientResolveArgs\n}): Promise<string | undefined> => {\n\tconst { configured, sources, sourceArgs } = opts\n\tif (!configured) {\n\t\treturn undefined\n\t}\n\tconst source = sources?.get(configured)\n\tif (!source) {\n\t\treturn configured\n\t}\n\t// A source only resolves with the run-time args; there are none at authoring/validation time.\n\tconst resolved = sourceArgs ? await source.resolve(sourceArgs) : null\n\tif (!resolved) {\n\t\treturn undefined\n\t}\n\treturn firstSender(resolved) || undefined\n}\n\n/** A bare plausible address, or a `Name <addr>` display form wrapping one (quotes and commas in the name included). */\nconst isPlausibleSender = (value: string): boolean => {\n\tif (isPlausibleEmail(value)) {\n\t\treturn true\n\t}\n\tconst bracketed = /^[^<>]*<([^<>\\s]+)>$/.exec(value)\n\treturn Boolean(bracketed?.[1] && isPlausibleEmail(bracketed[1]))\n}\n\n/**\n * The sender-side counterpart of `firstAddress`: one sender only, but `Name <addr>` display form\n * survives because that is the documented shape of a `from`. Order matters: cut at line breaks\n * first (the header-injection vector), accept the whole remaining line so a quoted display name\n * may contain commas, and only then comma-split to clamp a multi-address result to its first\n * entry. An implausible result becomes empty (send with the adapter default) rather than a\n * broken header.\n */\nconst firstSender = (value: string): string => {\n\tconst line = (value.split(/[\\n\\r]+/)[0] ?? '').trim()\n\tif (isPlausibleSender(line)) {\n\t\treturn line\n\t}\n\tconst [first] = line.split(/[,;]+/)\n\tconst cleaned = (first ?? '').trim()\n\treturn isPlausibleSender(cleaned) ? cleaned : ''\n}\n\n/**\n * Host seam resolving the selectable `from` addresses for `emailTeam`/`confirmation`\n * (plugin option `email.fromAddresses`). Multi-tenant hosts derive tenant scoping from `req`\n * (host header, cookie, or auth context) and return only that tenant's allowed senders. `value`\n * is the literal string handed to `payload.sendEmail`'s `from` (e.g. `'Name <addr@x.com>'` or a\n * plain address). Absent keeps the email adapter's default sender and adds no `from` field at all.\n */\nexport type FromAddressesResolver = (args: {\n\treq: PayloadRequest\n}) => Promise<FromAddressOption[]> | FromAddressOption[]\n\nconst FROM_FIELD_REF = '@10x-media/form-builder/client#EndpointOptionsSelect'\n\n/**\n * Validate for the `from` field, closed over the host resolver (mirrors the confirmation action's\n * `toField` and poll's `resultsField`): unset is fine, otherwise the value must be one of the\n * resolver's options for this request. A throwing resolver fails closed with a translated message\n * rather than surfacing a raw error on save.\n *\n * Failing closed has an operational cost worth knowing: Payload runs this on every save, not only\n * when `from` changed, so for as long as the resolver is down no form carrying an email action with\n * a `from` set can be saved at all, including edits that never touch the address. That is the\n * deliberate trade: failing open would persist a sender the host can no longer vouch for, and\n * unlike `toField` and `resultsField` this seam depends on host infrastructure that can be down.\n * A resolver reaching a flaky upstream should cache or fall back internally rather than throw.\n */\nexport const validateFromField =\n\t(resolver: FromAddressesResolver | undefined, sourceValues?: Set<string>) =>\n\tasync (value: unknown, { req }: { req: PayloadRequest }): Promise<string | true> => {\n\t\tif (typeof value !== 'string' || value.length === 0) {\n\t\t\treturn true\n\t\t}\n\t\t// A registered source value validates by membership alone, no resolver round trip.\n\t\tif (sourceValues?.has(value)) {\n\t\t\treturn true\n\t\t}\n\t\tif (!resolver) {\n\t\t\treturn asTranslate(req.t)(keys.validationFromUnknown)\n\t\t}\n\t\tlet options: FromAddressOption[]\n\t\ttry {\n\t\t\toptions = await resolver({ req })\n\t\t} catch {\n\t\t\treturn asTranslate(req.t)(keys.validationFromUnavailable)\n\t\t}\n\t\treturn options.some((option) => option.value === value)\n\t\t\t? true\n\t\t\t: asTranslate(req.t)(keys.validationFromUnknown)\n\t}\n\n/**\n * The `from` select shared by `emailTeam` and `confirmation`: an `EndpointOptionsSelect` backed by\n * the forms collection's `/from-addresses` endpoint (registered when `email.fromAddresses` or\n * `email.fromSources` is set). The option set is request-scoped, not per-form, so the select is\n * marked `scope: 'request'` and its options load while the form is still being created.\n */\nexport const buildFromField = (\n\tresolver: FromAddressesResolver | undefined,\n\tsources?: FromAddressSourceRegistry\n): TextField => ({\n\tname: 'from',\n\ttype: 'text',\n\tlabel: labelFor(keys.actionConfigFrom),\n\tvalidate: validateFromField(\n\t\tresolver,\n\t\tsources ? new Set(Object.values(sources).map((source) => source.value)) : undefined\n\t),\n\tadmin: {\n\t\tcomponents: {\n\t\t\tField: {\n\t\t\t\tpath: FROM_FIELD_REF,\n\t\t\t\tclientProps: {\n\t\t\t\t\tendpoint: 'from-addresses',\n\t\t\t\t\tscope: 'request',\n\t\t\t\t\tdescriptionKey: keys.actionConfigFromDescription,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t},\n})\n\nexport type ResolveFromAddressesRequestArgs = {\n\t/** Whether the caller is authenticated (an admin/user). */\n\tisAuthed: boolean\n\treq: PayloadRequest\n\tresolver?: FromAddressesResolver\n\tsources?: FromAddressSourceRegistry\n}\n\n/**\n * A source entry as the from select shows it. A string label is display text served raw (matching\n * how `RecipientsSelect` receives source labels); a per-locale record picks the request's admin\n * language, then English, then any value.\n */\nconst sourceOption = (source: FromAddressSource, req: PayloadRequest): FromAddressOption => {\n\tif (typeof source.label === 'string') {\n\t\treturn { label: source.label, value: source.value }\n\t}\n\tconst label =\n\t\tsource.label[req.i18n.language] ??\n\t\tsource.label.en ??\n\t\tObject.values(source.label)[0] ??\n\t\tsource.value\n\treturn { label, value: source.value }\n}\n\nexport type ResolveFromAddressesRequestResult = {\n\tstatus: number\n\tbody: { options: FromAddressOption[] } | { errors: { message: string }[] }\n}\n\n/**\n * Authorize and resolve the `GET /:id/from-addresses` request backing the `from` selects:\n * authenticated callers get the host resolver's current options for this request; anonymous\n * callers are always refused. The route id is unused (see `buildFromField`). Statuses mirror\n * the poll-options endpoint: 403 unauthenticated, 503 when the resolver throws (fail closed).\n */\nexport const resolveFromAddressesRequest = async (\n\targs: ResolveFromAddressesRequestArgs\n): Promise<ResolveFromAddressesRequestResult> => {\n\tconst { isAuthed, req, resolver, sources } = args\n\tif (!isAuthed) {\n\t\treturn { status: 403, body: { errors: [{ message: 'Forbidden' }] } }\n\t}\n\ttry {\n\t\t// Sources lead: the send-time-resolved sender is the tenant identity, static literals are\n\t\t// the exceptions an editor picks deliberately.\n\t\tconst sourceOptions = Object.values(sources ?? {}).map((source) => sourceOption(source, req))\n\t\tconst resolved = resolver ? await resolver({ req }) : []\n\t\treturn { status: 200, body: { options: [...sourceOptions, ...resolved] } }\n\t} catch {\n\t\treturn { status: 503, body: { errors: [{ message: 'From addresses unavailable' }] } }\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { SubmissionDescriptor, SubmissionValue } from "../submissions/types.js";
|
|
2
|
+
import { FormContextReference } from "../context/formContext.js";
|
|
3
|
+
import { Payload, PayloadRequest } from "payload";
|
|
4
|
+
|
|
5
|
+
//#region src/actions/recipientSources.d.ts
|
|
6
|
+
/** Arguments a recipient source's `resolve` receives when a submission's email actions run. */
|
|
7
|
+
type RecipientResolveArgs = {
|
|
8
|
+
/** The verified form-context reference, or null when the form was rendered without one. */context: FormContextReference | null;
|
|
9
|
+
values: SubmissionValue[];
|
|
10
|
+
descriptors: SubmissionDescriptor[];
|
|
11
|
+
form: {
|
|
12
|
+
id: number | string;
|
|
13
|
+
title?: string;
|
|
14
|
+
};
|
|
15
|
+
submissionId: number | string;
|
|
16
|
+
payload: Payload;
|
|
17
|
+
req?: PayloadRequest;
|
|
18
|
+
locale: string;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* A recipient the plugin resolves server-side at send time (plugin option `email.recipientSources`).
|
|
22
|
+
* `value` is the namespaced string stored in the recipient list (e.g. `context:pageContact`), so it
|
|
23
|
+
* cannot collide with an address; `label` is what the editor sees in the recipient field. `resolve`
|
|
24
|
+
* returns zero or more addresses, `[]` meaning "nothing to send to from this source", which is normal.
|
|
25
|
+
*/
|
|
26
|
+
type RecipientSource = {
|
|
27
|
+
value: string;
|
|
28
|
+
label: string | Record<string, string>;
|
|
29
|
+
resolve: (args: RecipientResolveArgs) => Promise<string[]> | string[];
|
|
30
|
+
};
|
|
31
|
+
type RecipientSourceRegistry = Record<string, RecipientSource>;
|
|
32
|
+
//#endregion
|
|
33
|
+
export { RecipientResolveArgs, RecipientSource, RecipientSourceRegistry };
|
|
34
|
+
//# sourceMappingURL=recipientSources.d.ts.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
//#region src/actions/recipientSources.ts
|
|
2
|
+
/** Index a source registry (recipient or from) by stored `value` for O(1) lookup during validation and resolution. */
|
|
3
|
+
const sourcesByValue = (registry) => {
|
|
4
|
+
const map = /* @__PURE__ */ new Map();
|
|
5
|
+
for (const source of Object.values(registry ?? {})) map.set(source.value, source);
|
|
6
|
+
return map;
|
|
7
|
+
};
|
|
8
|
+
//#endregion
|
|
9
|
+
export { sourcesByValue };
|
|
10
|
+
|
|
11
|
+
//# sourceMappingURL=recipientSources.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recipientSources.js","names":[],"sources":["../../src/actions/recipientSources.ts"],"sourcesContent":["import type { Payload, PayloadRequest } from 'payload'\nimport type { FormContextReference } from '../context/formContext'\nimport type { SubmissionDescriptor, SubmissionValue } from '../submissions/types'\n\n/** Arguments a recipient source's `resolve` receives when a submission's email actions run. */\nexport type RecipientResolveArgs = {\n\t/** The verified form-context reference, or null when the form was rendered without one. */\n\tcontext: FormContextReference | null\n\tvalues: SubmissionValue[]\n\tdescriptors: SubmissionDescriptor[]\n\tform: { id: number | string; title?: string }\n\tsubmissionId: number | string\n\tpayload: Payload\n\treq?: PayloadRequest\n\tlocale: string\n}\n\n/**\n * A recipient the plugin resolves server-side at send time (plugin option `email.recipientSources`).\n * `value` is the namespaced string stored in the recipient list (e.g. `context:pageContact`), so it\n * cannot collide with an address; `label` is what the editor sees in the recipient field. `resolve`\n * returns zero or more addresses, `[]` meaning \"nothing to send to from this source\", which is normal.\n */\nexport type RecipientSource = {\n\tvalue: string\n\tlabel: string | Record<string, string>\n\tresolve: (args: RecipientResolveArgs) => Promise<string[]> | string[]\n}\n\nexport type RecipientSourceRegistry = Record<string, RecipientSource>\n\n/** Index a source registry (recipient or from) by stored `value` for O(1) lookup during validation and resolution. */\nexport const sourcesByValue = <T extends { value: string }>(\n\tregistry?: Record<string, T>\n): Map<string, T> => {\n\tconst map = new Map<string, T>()\n\tfor (const source of Object.values(registry ?? {})) {\n\t\tmap.set(source.value, source)\n\t}\n\treturn map\n}\n"],"mappings":";;AAgCA,MAAa,kBACZ,aACoB;CACpB,MAAM,sBAAM,IAAI,IAAe;CAC/B,KAAK,MAAM,UAAU,OAAO,OAAO,YAAY,CAAC,CAAC,GAChD,IAAI,IAAI,OAAO,OAAO,MAAM;CAE7B,OAAO;AACR"}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { RegistryConfig } from "../plugin/applyRegistryConfig.js";
|
|
1
2
|
import { AnyActionDefinition } from "./defineAction.js";
|
|
2
3
|
|
|
3
4
|
//#region src/actions/registry.d.ts
|
|
4
5
|
type ActionRegistry = Map<string, AnyActionDefinition>;
|
|
5
6
|
/** `false` removes a built-in, `true` keeps it, a definition adds or replaces one. */
|
|
6
7
|
type ActionOption = boolean | AnyActionDefinition;
|
|
7
|
-
type ActionsConfig =
|
|
8
|
+
type ActionsConfig = RegistryConfig<AnyActionDefinition>;
|
|
8
9
|
/**
|
|
9
10
|
* Resolve the active action registry from built-in defaults and a consumer override map. `false`
|
|
10
11
|
* removes a type, `true` keeps the default (no-op when none exists), a definition adds or replaces.
|
package/dist/actions/registry.js
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
|
+
import { applyRegistryConfig } from "../plugin/applyRegistryConfig.js";
|
|
1
2
|
//#region src/actions/registry.ts
|
|
2
3
|
/**
|
|
3
4
|
* Resolve the active action registry from built-in defaults and a consumer override map. `false`
|
|
4
5
|
* removes a type, `true` keeps the default (no-op when none exists), a definition adds or replaces.
|
|
5
6
|
* Mirrors the field-type and validation-rule registry convention.
|
|
6
7
|
*/
|
|
7
|
-
const resolveActions = (defaults, config = {}) =>
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
else if (option === true) {} else registry.set(type, option);
|
|
11
|
-
return registry;
|
|
12
|
-
};
|
|
8
|
+
const resolveActions = (defaults, config = {}) => applyRegistryConfig(new Map(Object.entries(defaults)), config);
|
|
9
|
+
/** Whether a stored action instance's definition declares its failure to be the submission's failure. */
|
|
10
|
+
const isEssentialAction = (registry, instance) => registry.get(instance.blockType)?.essential === true;
|
|
13
11
|
//#endregion
|
|
14
|
-
export { resolveActions };
|
|
12
|
+
export { isEssentialAction, resolveActions };
|
|
15
13
|
|
|
16
14
|
//# sourceMappingURL=registry.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.js","names":[],"sources":["../../src/actions/registry.ts"],"sourcesContent":["import type { AnyActionDefinition } from './defineAction'\n\nexport type ActionRegistry = Map<string, AnyActionDefinition>\n\n/** `false` removes a built-in, `true` keeps it, a definition adds or replaces one. */\nexport type ActionOption = boolean | AnyActionDefinition\n\nexport type ActionsConfig =
|
|
1
|
+
{"version":3,"file":"registry.js","names":[],"sources":["../../src/actions/registry.ts"],"sourcesContent":["import { applyRegistryConfig, type RegistryConfig } from '../plugin/applyRegistryConfig'\nimport type { AnyActionDefinition } from './defineAction'\n\nexport type ActionRegistry = Map<string, AnyActionDefinition>\n\n/** `false` removes a built-in, `true` keeps it, a definition adds or replaces one. */\nexport type ActionOption = boolean | AnyActionDefinition\n\nexport type ActionsConfig = RegistryConfig<AnyActionDefinition>\n\n/**\n * Resolve the active action registry from built-in defaults and a consumer override map. `false`\n * removes a type, `true` keeps the default (no-op when none exists), a definition adds or replaces.\n * Mirrors the field-type and validation-rule registry convention.\n */\nexport const resolveActions = (\n\tdefaults: Record<string, AnyActionDefinition>,\n\tconfig: ActionsConfig = {}\n): ActionRegistry => applyRegistryConfig(new Map(Object.entries(defaults)), config)\n\n/** Whether a stored action instance's definition declares its failure to be the submission's failure. */\nexport const isEssentialAction = (\n\tregistry: ActionRegistry,\n\tinstance: { blockType: string }\n): boolean => registry.get(instance.blockType)?.essential === true\n"],"mappings":";;;;;;;AAeA,MAAa,kBACZ,UACA,SAAwB,CAAC,MACL,oBAAoB,IAAI,IAAI,OAAO,QAAQ,QAAQ,CAAC,GAAG,MAAM;;AAGlF,MAAa,qBACZ,UACA,aACa,SAAS,IAAI,SAAS,SAAS,GAAG,cAAc"}
|
|
@@ -1,10 +1,18 @@
|
|
|
1
|
+
import { makeRenderBody } from "./body/serializeBody.js";
|
|
1
2
|
//#region src/actions/runActions.ts
|
|
2
3
|
/**
|
|
3
4
|
* Run each configured action, isolating failures: a throwing action is captured as a failed result
|
|
4
5
|
* and never breaks the others or the caller.
|
|
5
6
|
*/
|
|
6
7
|
const runActions = async (args) => {
|
|
7
|
-
const { actions, registry, ...ctx } = args;
|
|
8
|
+
const { actions, registry, richText, ...ctx } = args;
|
|
9
|
+
const renderBody = makeRenderBody({
|
|
10
|
+
values: ctx.values,
|
|
11
|
+
descriptors: ctx.descriptors,
|
|
12
|
+
form: ctx.form,
|
|
13
|
+
req: ctx.req,
|
|
14
|
+
richText
|
|
15
|
+
});
|
|
8
16
|
const results = [];
|
|
9
17
|
for (const instance of actions) {
|
|
10
18
|
const definition = registry.get(instance.blockType);
|
|
@@ -12,6 +20,7 @@ const runActions = async (args) => {
|
|
|
12
20
|
try {
|
|
13
21
|
await definition.run({
|
|
14
22
|
...ctx,
|
|
23
|
+
renderBody,
|
|
15
24
|
config: instance
|
|
16
25
|
});
|
|
17
26
|
results.push({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runActions.js","names":[],"sources":["../../src/actions/runActions.ts"],"sourcesContent":["import type { ActionRunArgs } from './defineAction'\nimport type { ActionRegistry } from './registry'\n\n/** A stored action instance from the form's `actions` blocks array. */\nexport type ActionInstance = { blockType: string; [key: string]: unknown }\n\nexport type ActionResult = { type: string; ok: boolean; error?: string }\n\nexport type RunActionsArgs = Omit<ActionRunArgs, 'config'> & {\n\tactions: ActionInstance[]\n\tregistry: ActionRegistry\n}\n\n/**\n * Run each configured action, isolating failures: a throwing action is captured as a failed result\n * and never breaks the others or the caller.\n */\nexport const runActions = async (args: RunActionsArgs): Promise<ActionResult[]> => {\n\tconst { actions, registry, ...ctx } = args\n\tconst results: ActionResult[] = []\n\tfor (const instance of actions) {\n\t\tconst definition = registry.get(instance.blockType)\n\t\tif (!definition) {\n\t\t\tcontinue\n\t\t}\n\t\ttry {\n\t\t\tawait definition.run({ ...ctx, config: instance })\n\t\t\tresults.push({ type: instance.blockType, ok: true })\n\t\t} catch (error) {\n\t\t\tresults.push({\n\t\t\t\ttype: instance.blockType,\n\t\t\t\tok: false,\n\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t})\n\t\t}\n\t}\n\treturn results\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"runActions.js","names":[],"sources":["../../src/actions/runActions.ts"],"sourcesContent":["import type { RichTextBodyOption } from './body/serializeBody'\nimport { makeRenderBody } from './body/serializeBody'\nimport type { ActionRunArgs } from './defineAction'\nimport type { ActionRegistry } from './registry'\n\n/** A stored action instance from the form's `actions` blocks array. */\nexport type ActionInstance = { blockType: string; [key: string]: unknown }\n\nexport type ActionResult = { type: string; ok: boolean; error?: string }\n\nexport type RunActionsArgs = Omit<ActionRunArgs, 'config' | 'renderBody'> & {\n\tactions: ActionInstance[]\n\tregistry: ActionRegistry\n\trichText?: RichTextBodyOption\n}\n\n/**\n * Run each configured action, isolating failures: a throwing action is captured as a failed result\n * and never breaks the others or the caller.\n */\nexport const runActions = async (args: RunActionsArgs): Promise<ActionResult[]> => {\n\tconst { actions, registry, richText, ...ctx } = args\n\tconst renderBody = makeRenderBody({\n\t\tvalues: ctx.values,\n\t\tdescriptors: ctx.descriptors,\n\t\tform: ctx.form,\n\t\treq: ctx.req,\n\t\trichText,\n\t})\n\tconst results: ActionResult[] = []\n\tfor (const instance of actions) {\n\t\tconst definition = registry.get(instance.blockType)\n\t\tif (!definition) {\n\t\t\tcontinue\n\t\t}\n\t\ttry {\n\t\t\tawait definition.run({ ...ctx, renderBody, config: instance })\n\t\t\tresults.push({ type: instance.blockType, ok: true })\n\t\t} catch (error) {\n\t\t\tresults.push({\n\t\t\t\ttype: instance.blockType,\n\t\t\t\tok: false,\n\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t})\n\t\t}\n\t}\n\treturn results\n}\n"],"mappings":";;;;;;AAoBA,MAAa,aAAa,OAAO,SAAkD;CAClF,MAAM,EAAE,SAAS,UAAU,UAAU,GAAG,QAAQ;CAChD,MAAM,aAAa,eAAe;EACjC,QAAQ,IAAI;EACZ,aAAa,IAAI;EACjB,MAAM,IAAI;EACV,KAAK,IAAI;EACT;CACD,CAAC;CACD,MAAM,UAA0B,CAAC;CACjC,KAAK,MAAM,YAAY,SAAS;EAC/B,MAAM,aAAa,SAAS,IAAI,SAAS,SAAS;EAClD,IAAI,CAAC,YACJ;EAED,IAAI;GACH,MAAM,WAAW,IAAI;IAAE,GAAG;IAAK;IAAY,QAAQ;GAAS,CAAC;GAC7D,QAAQ,KAAK;IAAE,MAAM,SAAS;IAAW,IAAI;GAAK,CAAC;EACpD,SAAS,OAAO;GACf,QAAQ,KAAK;IACZ,MAAM,SAAS;IACf,IAAI;IACJ,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;GAC7D,CAAC;EACF;CACD;CACA,OAAO;AACR"}
|
package/dist/actions/task.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { asFieldTranslate } from "../translations/server.js";
|
|
2
|
+
import { isEssentialAction } from "./registry.js";
|
|
2
3
|
import { FORMS_SLUG } from "../collections/forms.js";
|
|
3
4
|
import { FORM_SUBMISSIONS_SLUG } from "../collections/formSubmissions.js";
|
|
4
5
|
import { runActions } from "./runActions.js";
|
|
@@ -7,21 +8,24 @@ const ACTIONS_TASK_SLUG = "form-builder-actions";
|
|
|
7
8
|
const asActions = (value) => Array.isArray(value) ? value : [];
|
|
8
9
|
const asValues = (value) => Array.isArray(value) ? value : [];
|
|
9
10
|
const asDescriptors = (value) => Array.isArray(value) ? value : [];
|
|
11
|
+
/** The verified `{ relationTo, value }` stored on a submission, or null when it carried no context. */
|
|
12
|
+
const asContext = (value) => {
|
|
13
|
+
if (value && typeof value === "object") {
|
|
14
|
+
const { relationTo, value: reference } = value;
|
|
15
|
+
if (typeof relationTo === "string" && (typeof reference === "string" || typeof reference === "number")) return {
|
|
16
|
+
relationTo,
|
|
17
|
+
value: reference
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
};
|
|
10
22
|
/**
|
|
11
23
|
* Load the form and submission by id and run the form's actions through the shared, failure-isolating
|
|
12
24
|
* `runActions`. Tolerates a missing form or submission (the row may have been deleted between enqueue and
|
|
13
25
|
* run) by returning early. Used by both the queued task handler and the inline fallback.
|
|
14
26
|
*/
|
|
15
27
|
const runActionsForSubmission = async (args) => {
|
|
16
|
-
const { input, registry, payload, req } = args;
|
|
17
|
-
const form = await payload.findByID({
|
|
18
|
-
collection: FORMS_SLUG,
|
|
19
|
-
id: input.formId,
|
|
20
|
-
depth: 0,
|
|
21
|
-
overrideAccess: true,
|
|
22
|
-
req
|
|
23
|
-
}).catch(() => null);
|
|
24
|
-
if (!form) return;
|
|
28
|
+
const { input, registry, payload, req, richText } = args;
|
|
25
29
|
const submission = await payload.findByID({
|
|
26
30
|
collection: FORM_SUBMISSIONS_SLUG,
|
|
27
31
|
id: input.submissionId,
|
|
@@ -29,12 +33,27 @@ const runActionsForSubmission = async (args) => {
|
|
|
29
33
|
overrideAccess: true,
|
|
30
34
|
req
|
|
31
35
|
}).catch(() => null);
|
|
32
|
-
if (!submission) return;
|
|
36
|
+
if (!submission) return [];
|
|
33
37
|
const locale = typeof submission.locale === "string" ? submission.locale : req?.locale ?? "en";
|
|
38
|
+
const form = await payload.findByID({
|
|
39
|
+
collection: FORMS_SLUG,
|
|
40
|
+
id: input.formId,
|
|
41
|
+
depth: 0,
|
|
42
|
+
overrideAccess: true,
|
|
43
|
+
locale,
|
|
44
|
+
req
|
|
45
|
+
}).catch(() => null);
|
|
46
|
+
if (!form) return [];
|
|
34
47
|
const t = asFieldTranslate(req?.i18n?.t ?? ((key) => key));
|
|
35
|
-
|
|
36
|
-
|
|
48
|
+
const subset = input.subset ?? "all";
|
|
49
|
+
const results = await runActions({
|
|
50
|
+
actions: asActions(form.actions).filter((instance) => {
|
|
51
|
+
if (subset === "all") return true;
|
|
52
|
+
const isEssential = isEssentialAction(registry, instance);
|
|
53
|
+
return subset === "essential" ? isEssential : !isEssential;
|
|
54
|
+
}),
|
|
37
55
|
registry,
|
|
56
|
+
richText,
|
|
38
57
|
form: {
|
|
39
58
|
id: form.id,
|
|
40
59
|
title: typeof form.title === "string" ? form.title : void 0
|
|
@@ -42,14 +61,25 @@ const runActionsForSubmission = async (args) => {
|
|
|
42
61
|
submissionId: submission.id,
|
|
43
62
|
values: asValues(submission.values),
|
|
44
63
|
descriptors: asDescriptors(submission.descriptors),
|
|
64
|
+
context: asContext(submission.context),
|
|
45
65
|
payload,
|
|
46
66
|
req,
|
|
47
67
|
locale,
|
|
48
68
|
t
|
|
49
69
|
});
|
|
70
|
+
for (const result of results) if (!result.ok) payload.logger?.error(`@10x-media/form-builder: action "${result.type}" failed for submission ${String(submission.id)}: ${result.error ?? "unknown error"}`);
|
|
71
|
+
if (subset !== "essential" && form.persistSubmissions === false) await payload.delete({
|
|
72
|
+
collection: FORM_SUBMISSIONS_SLUG,
|
|
73
|
+
id: submission.id,
|
|
74
|
+
overrideAccess: true,
|
|
75
|
+
req
|
|
76
|
+
}).catch((error) => {
|
|
77
|
+
payload.logger?.error(`@10x-media/form-builder: failed to prune submission ${String(submission.id)}: ${error instanceof Error ? error.message : String(error)}`);
|
|
78
|
+
});
|
|
79
|
+
return results;
|
|
50
80
|
};
|
|
51
81
|
/** Native Payload jobs task that runs a submission's post-submit actions out of band. */
|
|
52
|
-
const buildActionsTask = (registry) => ({
|
|
82
|
+
const buildActionsTask = (registry, richText) => ({
|
|
53
83
|
slug: ACTIONS_TASK_SLUG,
|
|
54
84
|
inputSchema: [{
|
|
55
85
|
name: "formId",
|
|
@@ -65,16 +95,17 @@ const buildActionsTask = (registry) => ({
|
|
|
65
95
|
input,
|
|
66
96
|
registry,
|
|
67
97
|
payload: req.payload,
|
|
68
|
-
req
|
|
98
|
+
req,
|
|
99
|
+
richText
|
|
69
100
|
});
|
|
70
101
|
return { output: {} };
|
|
71
102
|
}
|
|
72
103
|
});
|
|
73
104
|
/** Register the actions task on `config.jobs.tasks`, creating the jobs config if absent. */
|
|
74
|
-
const registerActionsTask = (config, registry) => {
|
|
105
|
+
const registerActionsTask = (config, registry, richText) => {
|
|
75
106
|
config.jobs ??= {};
|
|
76
107
|
config.jobs.tasks ??= [];
|
|
77
|
-
config.jobs.tasks.push(buildActionsTask(registry));
|
|
108
|
+
config.jobs.tasks.push(buildActionsTask(registry, richText));
|
|
78
109
|
};
|
|
79
110
|
//#endregion
|
|
80
111
|
export { ACTIONS_TASK_SLUG, registerActionsTask, runActionsForSubmission };
|
package/dist/actions/task.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task.js","names":[],"sources":["../../src/actions/task.ts"],"sourcesContent":["import type { Config, Payload, PayloadRequest, TaskConfig } from 'payload'\nimport { FORM_SUBMISSIONS_SLUG } from '../collections/formSubmissions'\nimport { FORMS_SLUG } from '../collections/forms'\nimport type { Translate } from '../fields/types'\nimport type { SubmissionDescriptor, SubmissionValue } from '../submissions/types'\nimport { asFieldTranslate } from '../translations/server'\nimport type { ActionRegistry } from './registry'\nimport type { ActionInstance } from './runActions'\nimport { runActions } from './runActions'\n\nexport const ACTIONS_TASK_SLUG = 'form-builder-actions'\n\n/** Input the dispatch path enqueues; the handler re-loads everything else from the DB. */\nexport type ActionsTaskInput = {
|
|
1
|
+
{"version":3,"file":"task.js","names":[],"sources":["../../src/actions/task.ts"],"sourcesContent":["import type { Config, Payload, PayloadRequest, TaskConfig, TypedLocale } from 'payload'\nimport { FORM_SUBMISSIONS_SLUG } from '../collections/formSubmissions'\nimport { FORMS_SLUG } from '../collections/forms'\nimport type { FormContextReference } from '../context/formContext'\nimport type { Translate } from '../fields/types'\nimport type { SubmissionDescriptor, SubmissionValue } from '../submissions/types'\nimport { asFieldTranslate } from '../translations/server'\nimport type { RichTextBodyOption } from './body/serializeBody'\nimport { type ActionRegistry, isEssentialAction } from './registry'\nimport type { ActionInstance, ActionResult } from './runActions'\nimport { runActions } from './runActions'\n\nexport const ACTIONS_TASK_SLUG = 'form-builder-actions'\n\n/** Input the dispatch path enqueues; the handler re-loads everything else from the DB. */\n/** `subset` filters by the action definitions' `essential` flag; absent runs everything ('all'). */\nexport type ActionsTaskInput = {\n\tformId: number | string\n\tsubmissionId: number | string\n\tsubset?: 'essential' | 'rest'\n}\n\nconst asActions = (value: unknown): ActionInstance[] =>\n\tArray.isArray(value) ? (value as ActionInstance[]) : []\n\nconst asValues = (value: unknown): SubmissionValue[] =>\n\tArray.isArray(value) ? (value as SubmissionValue[]) : []\n\nconst asDescriptors = (value: unknown): SubmissionDescriptor[] =>\n\tArray.isArray(value) ? (value as SubmissionDescriptor[]) : []\n\n/** The verified `{ relationTo, value }` stored on a submission, or null when it carried no context. */\nconst asContext = (value: unknown): FormContextReference | null => {\n\tif (value && typeof value === 'object') {\n\t\tconst { relationTo, value: reference } = value as Record<string, unknown>\n\t\tif (\n\t\t\ttypeof relationTo === 'string' &&\n\t\t\t(typeof reference === 'string' || typeof reference === 'number')\n\t\t) {\n\t\t\treturn { relationTo, value: reference }\n\t\t}\n\t}\n\treturn null\n}\n\n/**\n * Load the form and submission by id and run the form's actions through the shared, failure-isolating\n * `runActions`. Tolerates a missing form or submission (the row may have been deleted between enqueue and\n * run) by returning early. Used by both the queued task handler and the inline fallback.\n */\nexport const runActionsForSubmission = async (args: {\n\tinput: ActionsTaskInput\n\tregistry: ActionRegistry\n\tpayload: Payload\n\treq?: PayloadRequest\n\trichText?: RichTextBodyOption\n}): Promise<ActionResult[]> => {\n\tconst { input, registry, payload, req, richText } = args\n\tconst submission = await payload\n\t\t.findByID({\n\t\t\tcollection: FORM_SUBMISSIONS_SLUG,\n\t\t\tid: input.submissionId,\n\t\t\tdepth: 0,\n\t\t\toverrideAccess: true,\n\t\t\treq,\n\t\t})\n\t\t.catch(() => null)\n\tif (!submission) {\n\t\treturn []\n\t}\n\n\t// The submission's own stored locale (set from req.locale at submit) is authoritative, so the form\n\t// is loaded at it. A localized action config, notably the emailTeam `to`, then resolves to the\n\t// submission's locale even on the queued path, where the job runner's req may carry a different\n\t// (or no) locale than the visitor who submitted.\n\tconst locale = typeof submission.locale === 'string' ? submission.locale : (req?.locale ?? 'en')\n\n\tconst form = await payload\n\t\t.findByID({\n\t\t\tcollection: FORMS_SLUG,\n\t\t\tid: input.formId,\n\t\t\tdepth: 0,\n\t\t\toverrideAccess: true,\n\t\t\t// Cast: the stored locale is a plain string; a host's concrete locale union is unknowable from\n\t\t\t// the plugin, and an unrecognized code just falls back on read, so this narrows (zero runtime\n\t\t\t// delta) to satisfy a host whose `findByID` locale is a real union.\n\t\t\tlocale: locale as TypedLocale,\n\t\t\treq,\n\t\t})\n\t\t.catch(() => null)\n\tif (!form) {\n\t\treturn []\n\t}\n\n\tconst t: Translate = asFieldTranslate(req?.i18n?.t ?? ((key: string) => key))\n\n\tconst subset = input.subset ?? 'all'\n\tconst selected = asActions(form.actions).filter((instance) => {\n\t\tif (subset === 'all') {\n\t\t\treturn true\n\t\t}\n\t\tconst isEssential = isEssentialAction(registry, instance)\n\t\treturn subset === 'essential' ? isEssential : !isEssential\n\t})\n\n\tconst results = await runActions({\n\t\tactions: selected,\n\t\tregistry,\n\t\trichText,\n\t\tform: { id: form.id, title: typeof form.title === 'string' ? form.title : undefined },\n\t\tsubmissionId: submission.id,\n\t\tvalues: asValues(submission.values),\n\t\tdescriptors: asDescriptors(submission.descriptors),\n\t\tcontext: asContext(submission.context),\n\t\tpayload,\n\t\treq,\n\t\tlocale,\n\t\tt,\n\t})\n\t// A failed action (SMTP down, webhook non-2xx, missing adapter) is isolated per action; surface it\n\t// so a silently undelivered email/webhook is visible instead of the submission looking successful.\n\tfor (const result of results) {\n\t\tif (!result.ok) {\n\t\t\tpayload.logger?.error(\n\t\t\t\t`@10x-media/form-builder: action \"${result.type}\" failed for submission ${String(submission.id)}: ${result.error ?? 'unknown error'}`\n\t\t\t)\n\t\t}\n\t}\n\t// A form can opt out of storing submissions (a pure signup that only POSTs to a provider): prune the\n\t// row after the whole action pass, regardless of individual action success (every action already got\n\t// the values). Best-effort: a delete failure is logged, never thrown. Uploads referenced in the values\n\t// are host-owned and not cascaded (documented).\n\t// Never on the essential pass: essential actions run first and their failure keeps the row (the\n\t// dispatcher then skips this completion entirely), so pruning belongs to the closing pass alone.\n\tif (subset !== 'essential' && form.persistSubmissions === false) {\n\t\tawait payload\n\t\t\t.delete({ collection: FORM_SUBMISSIONS_SLUG, id: submission.id, overrideAccess: true, req })\n\t\t\t.catch((error) => {\n\t\t\t\tpayload.logger?.error(\n\t\t\t\t\t`@10x-media/form-builder: failed to prune submission ${String(submission.id)}: ${\n\t\t\t\t\t\terror instanceof Error ? error.message : String(error)\n\t\t\t\t\t}`\n\t\t\t\t)\n\t\t\t})\n\t}\n\treturn results\n}\n\n/** Native Payload jobs task that runs a submission's post-submit actions out of band. */\nexport const buildActionsTask = (\n\tregistry: ActionRegistry,\n\trichText?: RichTextBodyOption\n): TaskConfig =>\n\t({\n\t\tslug: ACTIONS_TASK_SLUG,\n\t\tinputSchema: [\n\t\t\t{ name: 'formId', type: 'text', required: true },\n\t\t\t{ name: 'submissionId', type: 'text', required: true },\n\t\t],\n\t\thandler: async ({ input, req }) => {\n\t\t\tawait runActionsForSubmission({\n\t\t\t\tinput: input as ActionsTaskInput,\n\t\t\t\tregistry,\n\t\t\t\tpayload: req.payload,\n\t\t\t\treq,\n\t\t\t\trichText,\n\t\t\t})\n\t\t\treturn { output: {} }\n\t\t},\n\t}) as TaskConfig\n\n/** Register the actions task on `config.jobs.tasks`, creating the jobs config if absent. */\nexport const registerActionsTask = (\n\tconfig: Config,\n\tregistry: ActionRegistry,\n\trichText?: RichTextBodyOption\n): void => {\n\tconfig.jobs ??= {}\n\tconfig.jobs.tasks ??= []\n\tconfig.jobs.tasks.push(buildActionsTask(registry, richText))\n}\n"],"mappings":"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"}
|
|
@@ -11,6 +11,15 @@ type AggregateFormResponsesArgs = {
|
|
|
11
11
|
req?: PayloadRequest; /** Safety cap on submissions scanned; beyond it the result is flagged `truncated`. Default 10000. */
|
|
12
12
|
maxSubmissions?: number; /** Page size for the submission scan. Default 500. */
|
|
13
13
|
pageSize?: number;
|
|
14
|
+
/**
|
|
15
|
+
* Source-resolved options by field name (an option-source poll's results field). They replace
|
|
16
|
+
* the instance's authored options for bucket seeding, order, and labels, and mark the field
|
|
17
|
+
* enumerable for the default field selection.
|
|
18
|
+
*/
|
|
19
|
+
resolvedOptions?: Record<string, {
|
|
20
|
+
value: string;
|
|
21
|
+
label: string;
|
|
22
|
+
}[]>;
|
|
14
23
|
};
|
|
15
24
|
/** True when a field declares non-empty options (a choice field safe to aggregate publicly). */
|
|
16
25
|
declare const fieldHasOptions: (field: FormFieldInstance) => boolean;
|
|
@@ -1,22 +1,16 @@
|
|
|
1
|
+
import { isNamedField } from "../fields/fieldKey.js";
|
|
2
|
+
import { instanceOptionsOf } from "../fields/instanceOptions.js";
|
|
1
3
|
import { aggregateRowsForFields } from "./aggregateRows.js";
|
|
2
4
|
import { FORMS_SLUG } from "../collections/forms.js";
|
|
3
5
|
import { FORM_SUBMISSIONS_SLUG } from "../collections/formSubmissions.js";
|
|
4
6
|
//#region src/aggregation/aggregateResponses.ts
|
|
5
|
-
const optionsOf = (field) => {
|
|
6
|
-
if (!Array.isArray(field.options)) return;
|
|
7
|
-
const options = field.options.filter((option) => typeof option?.value === "string").map((option) => ({
|
|
8
|
-
value: String(option.value),
|
|
9
|
-
label: option.label ?? String(option.value)
|
|
10
|
-
}));
|
|
11
|
-
return options.length > 0 ? options : void 0;
|
|
12
|
-
};
|
|
13
7
|
/** True when a field declares non-empty options (a choice field safe to aggregate publicly). */
|
|
14
|
-
const fieldHasOptions = (field) =>
|
|
15
|
-
const metaFor = (field) => ({
|
|
8
|
+
const fieldHasOptions = (field) => instanceOptionsOf(field) !== void 0;
|
|
9
|
+
const metaFor = (field, resolved) => ({
|
|
16
10
|
field: field.name,
|
|
17
11
|
label: field.label ?? field.name,
|
|
18
12
|
fieldType: field.blockType,
|
|
19
|
-
options:
|
|
13
|
+
options: resolved && resolved.length > 0 ? resolved : instanceOptionsOf(field)
|
|
20
14
|
});
|
|
21
15
|
/**
|
|
22
16
|
* Aggregate submission responses for a form. Loads the form for current labels + option order, then pages
|
|
@@ -25,7 +19,7 @@ const metaFor = (field) => ({
|
|
|
25
19
|
* `truncated`. Returns one `FieldAggregation` per requested field (or per enumerable field by default).
|
|
26
20
|
*/
|
|
27
21
|
const aggregateFormResponses = async (args) => {
|
|
28
|
-
const { payload, formId, fields, status = "complete", req, maxSubmissions = 1e4, pageSize = 500 } = args;
|
|
22
|
+
const { payload, formId, fields, status = "complete", req, maxSubmissions = 1e4, pageSize = 500, resolvedOptions } = args;
|
|
29
23
|
const form = await payload.findByID({
|
|
30
24
|
collection: FORMS_SLUG,
|
|
31
25
|
id: formId,
|
|
@@ -35,9 +29,9 @@ const aggregateFormResponses = async (args) => {
|
|
|
35
29
|
}).catch(() => null);
|
|
36
30
|
if (!form || !Array.isArray(form.fields)) return [];
|
|
37
31
|
const instances = form.fields;
|
|
38
|
-
const selected = fields ? instances.filter((field) => fields.includes(field.name)) : instances.filter((field) =>
|
|
32
|
+
const selected = fields ? instances.filter((field) => isNamedField(field) && fields.includes(field.name)) : instances.filter((field) => isNamedField(field) && (instanceOptionsOf(field) !== void 0 || (resolvedOptions?.[field.name]?.length ?? 0) > 0));
|
|
39
33
|
if (selected.length === 0) return [];
|
|
40
|
-
const metas = selected.map(metaFor);
|
|
34
|
+
const metas = selected.map((field) => metaFor(field, resolvedOptions?.[field.name]));
|
|
41
35
|
const where = { and: [{ form: { equals: formId } }, ...status === "all" ? [] : [{ status: { equals: status } }]] };
|
|
42
36
|
const rows = [];
|
|
43
37
|
let page = 1;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aggregateResponses.js","names":[],"sources":["../../src/aggregation/aggregateResponses.ts"],"sourcesContent":["import type { Payload, PayloadRequest } from 'payload'\nimport { FORM_SUBMISSIONS_SLUG } from '../collections/formSubmissions'\nimport { FORMS_SLUG } from '../collections/forms'\nimport type { FormFieldInstance } from '../submissions/types'\nimport { aggregateRowsForFields } from './aggregateRows'\nimport type { AggregationRow, FieldAggregation, FieldMeta, SubmissionStatusFilter } from './types'\n\nexport type AggregateFormResponsesArgs = {\n\tpayload: Payload\n\tformId: number | string\n\t/** Field machine names to aggregate. Defaults to every field that declares options (enumerable). */\n\tfields?: string[]\n\t/** Submission status to count. Defaults to `complete`. */\n\tstatus?: SubmissionStatusFilter\n\treq?: PayloadRequest\n\t/** Safety cap on submissions scanned; beyond it the result is flagged `truncated`. Default 10000. */\n\tmaxSubmissions?: number\n\t/** Page size for the submission scan. Default 500. */\n\tpageSize?: number\n
|
|
1
|
+
{"version":3,"file":"aggregateResponses.js","names":[],"sources":["../../src/aggregation/aggregateResponses.ts"],"sourcesContent":["import type { Payload, PayloadRequest } from 'payload'\nimport { FORM_SUBMISSIONS_SLUG } from '../collections/formSubmissions'\nimport { FORMS_SLUG } from '../collections/forms'\nimport { isNamedField, type NamedFormFieldInstance } from '../fields/fieldKey'\nimport { instanceOptionsOf } from '../fields/instanceOptions'\nimport type { FormFieldInstance } from '../submissions/types'\nimport { aggregateRowsForFields } from './aggregateRows'\nimport type { AggregationRow, FieldAggregation, FieldMeta, SubmissionStatusFilter } from './types'\n\nexport type AggregateFormResponsesArgs = {\n\tpayload: Payload\n\tformId: number | string\n\t/** Field machine names to aggregate. Defaults to every field that declares options (enumerable). */\n\tfields?: string[]\n\t/** Submission status to count. Defaults to `complete`. */\n\tstatus?: SubmissionStatusFilter\n\treq?: PayloadRequest\n\t/** Safety cap on submissions scanned; beyond it the result is flagged `truncated`. Default 10000. */\n\tmaxSubmissions?: number\n\t/** Page size for the submission scan. Default 500. */\n\tpageSize?: number\n\t/**\n\t * Source-resolved options by field name (an option-source poll's results field). They replace\n\t * the instance's authored options for bucket seeding, order, and labels, and mark the field\n\t * enumerable for the default field selection.\n\t */\n\tresolvedOptions?: Record<string, { value: string; label: string }[]>\n}\n\n/** True when a field declares non-empty options (a choice field safe to aggregate publicly). */\nexport const fieldHasOptions = (field: FormFieldInstance): boolean =>\n\tinstanceOptionsOf(field) !== undefined\n\nconst metaFor = (\n\tfield: NamedFormFieldInstance,\n\tresolved?: { value: string; label: string }[]\n): FieldMeta => ({\n\tfield: field.name,\n\tlabel: field.label ?? field.name,\n\tfieldType: field.blockType,\n\toptions: resolved && resolved.length > 0 ? resolved : instanceOptionsOf(field),\n})\n\n/**\n * Aggregate submission responses for a form. Loads the form for current labels + option order, then pages\n * `payload.find` over its submissions (JSON `values` cannot be GROUP BY'd portably across Mongo + Postgres,\n * so we scan + reduce in JS, cross-DB-identically). Bounded by `maxSubmissions`; beyond it the result is\n * `truncated`. Returns one `FieldAggregation` per requested field (or per enumerable field by default).\n */\nexport const aggregateFormResponses = async (\n\targs: AggregateFormResponsesArgs\n): Promise<FieldAggregation[]> => {\n\tconst {\n\t\tpayload,\n\t\tformId,\n\t\tfields,\n\t\tstatus = 'complete',\n\t\treq,\n\t\tmaxSubmissions = 10000,\n\t\tpageSize = 500,\n\t\tresolvedOptions,\n\t} = args\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 || !Array.isArray(form.fields)) {\n\t\treturn []\n\t}\n\tconst instances = form.fields as FormFieldInstance[]\n\tconst selected = fields\n\t\t? instances.filter(\n\t\t\t\t(field): field is NamedFormFieldInstance =>\n\t\t\t\t\tisNamedField(field) && fields.includes(field.name)\n\t\t\t)\n\t\t: instances.filter(\n\t\t\t\t(field): field is NamedFormFieldInstance =>\n\t\t\t\t\tisNamedField(field) &&\n\t\t\t\t\t(instanceOptionsOf(field) !== undefined ||\n\t\t\t\t\t\t(resolvedOptions?.[field.name]?.length ?? 0) > 0)\n\t\t\t)\n\tif (selected.length === 0) {\n\t\treturn []\n\t}\n\tconst metas = selected.map((field) => metaFor(field, resolvedOptions?.[field.name]))\n\n\tconst statusWhere = status === 'all' ? [] : [{ status: { equals: status } }]\n\tconst where = { and: [{ form: { equals: formId } }, ...statusWhere] }\n\n\tconst rows: AggregationRow[] = []\n\tlet page = 1\n\tlet truncated = false\n\tfor (;;) {\n\t\tconst result = await payload.find({\n\t\t\tcollection: FORM_SUBMISSIONS_SLUG,\n\t\t\twhere,\n\t\t\tlimit: pageSize,\n\t\t\tpage,\n\t\t\tdepth: 0,\n\t\t\toverrideAccess: true,\n\t\t\treq,\n\t\t\tselect: { values: true, descriptors: true },\n\t\t})\n\t\tfor (const doc of result.docs as AggregationRow[]) {\n\t\t\t// Check before pushing so `truncated` means \"matching rows were left out\", not \"the cap was reached\n\t\t\t// exactly\": a form with precisely `maxSubmissions` responses is complete, not a sample.\n\t\t\tif (rows.length >= maxSubmissions) {\n\t\t\t\ttruncated = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t\trows.push({ values: doc.values, descriptors: doc.descriptors })\n\t\t}\n\t\tif (truncated || !result.hasNextPage) {\n\t\t\tbreak\n\t\t}\n\t\tpage += 1\n\t}\n\n\treturn aggregateRowsForFields(rows, metas, truncated)\n}\n\nexport type AggregateFieldResponsesArgs = Omit<AggregateFormResponsesArgs, 'fields'> & {\n\tfield: string\n}\n\n/** Single-field convenience over `aggregateFormResponses`. Returns the field's aggregation, or null if absent. */\nexport const aggregateFieldResponses = async (\n\targs: AggregateFieldResponsesArgs\n): Promise<FieldAggregation | null> => {\n\tconst { field, ...rest } = args\n\tconst [result] = await aggregateFormResponses({ ...rest, fields: [field] })\n\treturn result ?? null\n}\n"],"mappings":";;;;;;;AA8BA,MAAa,mBAAmB,UAC/B,kBAAkB,KAAK,MAAM,KAAA;AAE9B,MAAM,WACL,OACA,cACgB;CAChB,OAAO,MAAM;CACb,OAAO,MAAM,SAAS,MAAM;CAC5B,WAAW,MAAM;CACjB,SAAS,YAAY,SAAS,SAAS,IAAI,WAAW,kBAAkB,KAAK;AAC9E;;;;;;;AAQA,MAAa,yBAAyB,OACrC,SACiC;CACjC,MAAM,EACL,SACA,QACA,QACA,SAAS,YACT,KACA,iBAAiB,KACjB,WAAW,KACX,oBACG;CACJ,MAAM,OAAO,MAAM,QACjB,SAAS;EAAE,YAAY;EAAY,IAAI;EAAQ,OAAO;EAAG,gBAAgB;EAAM;CAAI,CAAC,EACpF,YAAY,IAAI;CAClB,IAAI,CAAC,QAAQ,CAAC,MAAM,QAAQ,KAAK,MAAM,GACtC,OAAO,CAAC;CAET,MAAM,YAAY,KAAK;CACvB,MAAM,WAAW,SACd,UAAU,QACT,UACA,aAAa,KAAK,KAAK,OAAO,SAAS,MAAM,IAAI,CACnD,IACC,UAAU,QACT,UACA,aAAa,KAAK,MACjB,kBAAkB,KAAK,MAAM,KAAA,MAC5B,kBAAkB,MAAM,OAAO,UAAU,KAAK,EAClD;CACF,IAAI,SAAS,WAAW,GACvB,OAAO,CAAC;CAET,MAAM,QAAQ,SAAS,KAAK,UAAU,QAAQ,OAAO,kBAAkB,MAAM,KAAK,CAAC;CAGnF,MAAM,QAAQ,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,QAAQ,OAAO,EAAE,GAAG,GADhC,WAAW,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,QAAQ,OAAO,EAAE,CAAC,CACT,EAAE;CAEpE,MAAM,OAAyB,CAAC;CAChC,IAAI,OAAO;CACX,IAAI,YAAY;CAChB,SAAS;EACR,MAAM,SAAS,MAAM,QAAQ,KAAK;GACjC,YAAY;GACZ;GACA,OAAO;GACP;GACA,OAAO;GACP,gBAAgB;GAChB;GACA,QAAQ;IAAE,QAAQ;IAAM,aAAa;GAAK;EAC3C,CAAC;EACD,KAAK,MAAM,OAAO,OAAO,MAA0B;GAGlD,IAAI,KAAK,UAAU,gBAAgB;IAClC,YAAY;IACZ;GACD;GACA,KAAK,KAAK;IAAE,QAAQ,IAAI;IAAQ,aAAa,IAAI;GAAY,CAAC;EAC/D;EACA,IAAI,aAAa,CAAC,OAAO,aACxB;EAED,QAAQ;CACT;CAEA,OAAO,uBAAuB,MAAM,OAAO,SAAS;AACrD;;AAOA,MAAa,0BAA0B,OACtC,SACsC;CACtC,MAAM,EAAE,OAAO,GAAG,SAAS;CAC3B,MAAM,CAAC,UAAU,MAAM,uBAAuB;EAAE,GAAG;EAAM,QAAQ,CAAC,KAAK;CAAE,CAAC;CAC1E,OAAO,UAAU;AAClB"}
|