@10x-media/form-builder 0.1.0-beta.0 → 0.1.0-beta.10
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 +345 -0
- package/README.md +45 -1402
- 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 +22 -0
- package/dist/actions/buildActionBlocks.js.map +1 -0
- package/dist/actions/builtin/confirmation.js +52 -0
- package/dist/actions/builtin/confirmation.js.map +1 -0
- package/dist/actions/builtin/emailAction.d.ts +17 -0
- package/dist/actions/builtin/emailAction.js +83 -0
- package/dist/actions/builtin/emailAction.js.map +1 -0
- package/dist/actions/builtin/emailTeam.js +22 -0
- package/dist/actions/builtin/emailTeam.js.map +1 -0
- package/dist/actions/builtin/index.d.ts +22 -0
- package/dist/actions/builtin/index.js +27 -0
- package/dist/actions/builtin/index.js.map +1 -0
- package/dist/actions/builtin/signedWebhook.js +53 -0
- package/dist/actions/builtin/signedWebhook.js.map +1 -0
- package/dist/actions/defineAction.d.ts +37 -0
- package/dist/actions/defineAction.js +6 -0
- package/dist/actions/defineAction.js.map +1 -0
- package/dist/actions/dispatch.js +49 -0
- package/dist/actions/dispatch.js.map +1 -0
- package/dist/actions/emailRecipients.d.ts +12 -0
- package/dist/actions/emailRecipients.js +116 -0
- package/dist/actions/emailRecipients.js.map +1 -0
- package/dist/actions/fromAddresses.d.ts +21 -0
- package/dist/actions/fromAddresses.js +74 -0
- package/dist/actions/fromAddresses.js.map +1 -0
- package/dist/actions/registry.d.ts +17 -0
- package/dist/actions/registry.js +12 -0
- package/dist/actions/registry.js.map +1 -0
- package/dist/actions/runActions.d.ts +9 -0
- package/dist/actions/runActions.js +43 -0
- package/dist/actions/runActions.js.map +1 -0
- package/dist/actions/sign.d.ts +7 -0
- package/dist/actions/sign.js +9 -0
- package/dist/actions/sign.js.map +1 -0
- package/dist/actions/task.js +87 -0
- package/dist/actions/task.js.map +1 -0
- package/dist/aggregation/aggregateResponses.d.ts +40 -0
- package/dist/aggregation/aggregateResponses.js +80 -0
- package/dist/aggregation/aggregateResponses.js.map +1 -0
- package/dist/aggregation/aggregateRows.d.ts +10 -0
- package/dist/aggregation/aggregateRows.js +64 -0
- package/dist/aggregation/aggregateRows.js.map +1 -0
- package/dist/aggregation/resolveResultsRequest.d.ts +61 -0
- package/dist/aggregation/resolveResultsRequest.js +113 -0
- package/dist/aggregation/resolveResultsRequest.js.map +1 -0
- package/dist/aggregation/types.d.ts +39 -0
- package/dist/calc/computeCalcFields.d.ts +15 -0
- package/dist/calc/computeCalcFields.js +28 -0
- package/dist/calc/computeCalcFields.js.map +1 -0
- package/dist/calc/evaluate.d.ts +8 -0
- package/dist/calc/evaluate.js +58 -0
- package/dist/calc/evaluate.js.map +1 -0
- package/dist/calc/normalizeCalc.d.ts +8 -0
- package/dist/calc/normalizeCalc.js +87 -0
- package/dist/calc/normalizeCalc.js.map +1 -0
- package/dist/calc/types.d.ts +34 -0
- package/dist/calc/types.js +22 -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/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 +122 -0
- package/dist/client/ConditionBuilder.js.map +1 -0
- package/dist/client/ConditionRow.js +150 -0
- package/dist/client/ConditionRow.js.map +1 -0
- 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 +46 -0
- package/dist/client/EndpointOptionsSelect.js +106 -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 +15 -0
- package/dist/client/FlowBuilder.js +457 -0
- package/dist/client/FlowBuilder.js.map +1 -0
- package/dist/client/FormConditionField.d.ts +21 -0
- package/dist/client/FormConditionField.js +30 -0
- package/dist/client/FormConditionField.js.map +1 -0
- package/dist/client/RecipientsSelect.d.ts +31 -0
- package/dist/client/RecipientsSelect.js +174 -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 +275 -0
- package/dist/client/endpointOptions.js +29 -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 +57 -0
- package/dist/client/synthesizeClientField.js.map +1 -0
- 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 +204 -0
- package/dist/collections/formSubmissions.js.map +1 -0
- package/dist/collections/forms.js +482 -0
- package/dist/collections/forms.js.map +1 -0
- package/dist/collections/formsEndpoints.js +101 -0
- package/dist/collections/formsEndpoints.js.map +1 -0
- package/dist/collections/redirectFields.d.ts +29 -0
- package/dist/conditions/conditionType.js +21 -0
- package/dist/conditions/conditionType.js.map +1 -0
- package/dist/conditions/evaluate.d.ts +17 -0
- package/dist/conditions/evaluate.js +94 -0
- package/dist/conditions/evaluate.js.map +1 -0
- package/dist/conditions/fieldTypes.d.ts +11 -0
- package/dist/{fieldTypes-CzhhJXjg.js → conditions/fieldTypes.js} +6 -3
- package/dist/conditions/fieldTypes.js.map +1 -0
- package/dist/conditions/normalizeConditions.js +61 -0
- package/dist/conditions/normalizeConditions.js.map +1 -0
- package/dist/conditions/types.d.ts +8 -0
- 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 +55 -0
- package/dist/consent/captureConsent.js +73 -0
- package/dist/consent/captureConsent.js.map +1 -0
- package/dist/consent/consentSourcesField.d.ts +66 -0
- package/dist/consent/consentSourcesField.js +90 -0
- package/dist/consent/consentSourcesField.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 +44 -0
- package/dist/consent/resolveConsentStatements.js.map +1 -0
- package/dist/consent/resolvePublishedVersionRef.d.ts +23 -0
- package/dist/consent/resolvePublishedVersionRef.js +30 -0
- package/dist/consent/resolvePublishedVersionRef.js.map +1 -0
- package/dist/consent/types.d.ts +61 -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/events/noopSink.js +6 -0
- package/dist/events/noopSink.js.map +1 -0
- package/dist/events/resolveEventSink.js +8 -0
- package/dist/events/resolveEventSink.js.map +1 -0
- package/dist/events/types.d.ts +31 -0
- package/dist/exports/client.d.ts +15 -21
- package/dist/exports/client.js +15 -319
- package/dist/exports/i18n.d.ts +6 -138
- package/dist/exports/i18n.js +6 -3
- package/dist/exports/react.d.ts +63 -550
- package/dist/exports/react.js +55 -1563
- package/dist/exports/rsc.d.ts +9 -16
- package/dist/exports/rsc.js +9 -55
- package/dist/exports/types.d.ts +36 -5
- package/dist/fields/buildFieldBlocks.js +80 -0
- package/dist/fields/buildFieldBlocks.js.map +1 -0
- package/dist/fields/builtin/calculation.js +144 -0
- package/dist/fields/builtin/calculation.js.map +1 -0
- package/dist/fields/builtin/checkbox.js +21 -0
- package/dist/fields/builtin/checkbox.js.map +1 -0
- package/dist/fields/builtin/consent.js +47 -0
- package/dist/fields/builtin/consent.js.map +1 -0
- 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 +18 -0
- package/dist/fields/builtin/email.js.map +1 -0
- package/dist/fields/builtin/file.d.ts +15 -0
- package/dist/fields/builtin/file.js +127 -0
- package/dist/fields/builtin/file.js.map +1 -0
- package/dist/fields/builtin/index.d.ts +26 -0
- package/dist/fields/builtin/index.js +49 -0
- package/dist/fields/builtin/index.js.map +1 -0
- package/dist/fields/builtin/message.js +30 -0
- package/dist/fields/builtin/message.js.map +1 -0
- package/dist/fields/builtin/number.js +17 -0
- package/dist/fields/builtin/number.js.map +1 -0
- package/dist/fields/builtin/repeater.js +49 -0
- package/dist/fields/builtin/repeater.js.map +1 -0
- package/dist/fields/builtin/select.js +49 -0
- package/dist/fields/builtin/select.js.map +1 -0
- 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 +13 -0
- package/dist/fields/builtin/text.js.map +1 -0
- package/dist/fields/builtin/textarea.js +13 -0
- package/dist/fields/builtin/textarea.js.map +1 -0
- 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/defineFormField.d.ts +13 -0
- package/dist/fields/defineFormField.js +12 -0
- package/dist/fields/defineFormField.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 +12 -0
- package/dist/fields/registry.js +39 -0
- package/dist/fields/registry.js.map +1 -0
- package/dist/fields/sharedConfig.js +151 -0
- package/dist/fields/sharedConfig.js.map +1 -0
- package/dist/fields/types.d.ts +152 -0
- package/dist/flow/engine.d.ts +19 -0
- package/dist/flow/engine.js +29 -0
- package/dist/flow/engine.js.map +1 -0
- package/dist/flow/normalizeFlow.js +54 -0
- package/dist/flow/normalizeFlow.js.map +1 -0
- package/dist/flow/stepLabel.js +15 -0
- package/dist/flow/stepLabel.js.map +1 -0
- package/dist/flow/types.d.ts +32 -0
- package/dist/flow/types.js +11 -0
- package/dist/flow/types.js.map +1 -0
- package/dist/form/pollState.d.ts +22 -0
- package/dist/form/pollState.js +17 -0
- package/dist/form/pollState.js.map +1 -0
- package/dist/form/toFormDocument.d.ts +102 -0
- package/dist/form/toFormDocument.js +87 -0
- package/dist/form/toFormDocument.js.map +1 -0
- package/dist/form/types.d.ts +65 -0
- package/dist/index.d.ts +88 -4
- package/dist/index.js +111 -1898
- package/dist/index.js.map +1 -1
- package/dist/options.d.ts +229 -0
- package/dist/plugin/access.js +12 -0
- package/dist/plugin/access.js.map +1 -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/collectionOverrides.d.ts +22 -0
- package/dist/plugin/customState.js +26 -0
- package/dist/plugin/customState.js.map +1 -0
- package/dist/plugin/registerCollections.js +60 -0
- package/dist/plugin/registerCollections.js.map +1 -0
- package/dist/plugin/registerTranslations.js +19 -0
- package/dist/plugin/registerTranslations.js.map +1 -0
- 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 +52 -0
- package/dist/poll/closeJob.js +119 -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 +105 -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 +34 -0
- package/dist/poll/resolvePollOutcome.js +76 -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/prefill/valuesFromSearchParams.d.ts +18 -0
- package/dist/prefill/valuesFromSearchParams.js +41 -0
- package/dist/prefill/valuesFromSearchParams.js.map +1 -0
- package/dist/presentations/defaults.d.ts +33 -0
- package/dist/presentations/defaults.js +35 -0
- package/dist/presentations/defaults.js.map +1 -0
- package/dist/presentations/types.d.ts +19 -0
- package/dist/react/Form.d.ts +128 -0
- package/dist/react/Form.js +619 -0
- package/dist/react/Form.js.map +1 -0
- package/dist/react/FormContext.d.ts +59 -0
- package/dist/react/FormContext.js +14 -0
- package/dist/react/FormContext.js.map +1 -0
- 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 +22 -0
- package/dist/react/FormLayout.js +15 -0
- package/dist/react/FormLayout.js.map +1 -0
- package/dist/react/FormResults.d.ts +28 -0
- package/dist/react/FormResults.js +93 -0
- package/dist/react/FormResults.js.map +1 -0
- 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/Honeypot.d.ts +22 -0
- package/dist/react/Honeypot.js +38 -0
- package/dist/react/Honeypot.js.map +1 -0
- package/dist/react/Poll.d.ts +43 -0
- package/dist/react/Poll.js +135 -0
- package/dist/react/Poll.js.map +1 -0
- package/dist/react/captcha/HcaptchaCaptcha.d.ts +28 -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 +41 -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 +28 -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 +45 -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/cn.d.ts +6 -0
- package/dist/react/cn.js +8 -0
- package/dist/react/cn.js.map +1 -0
- package/dist/react/contract.d.ts +31 -0
- package/dist/react/contract.js +7 -0
- package/dist/react/contract.js.map +1 -0
- package/dist/react/events.js +14 -0
- package/dist/react/events.js.map +1 -0
- package/dist/react/fetchResults.d.ts +25 -0
- package/dist/react/fetchResults.js +31 -0
- package/dist/react/fetchResults.js.map +1 -0
- package/dist/react/presentation/Backdrop.d.ts +18 -0
- package/dist/react/presentation/Backdrop.js +14 -0
- package/dist/react/presentation/Backdrop.js.map +1 -0
- package/dist/react/presentation/DialogSurface.d.ts +35 -0
- package/dist/react/presentation/DialogSurface.js +53 -0
- package/dist/react/presentation/DialogSurface.js.map +1 -0
- package/dist/react/presentation/presentations.d.ts +7 -0
- package/dist/react/presentation/presentations.js +32 -0
- package/dist/react/presentation/presentations.js.map +1 -0
- package/dist/react/presentation/registry.d.ts +11 -0
- package/dist/react/presentation/registry.js +12 -0
- package/dist/react/presentation/registry.js.map +1 -0
- package/dist/react/presentation/types.d.ts +19 -0
- package/dist/react/presentation/useDismiss.d.ts +19 -0
- package/dist/react/presentation/useDismiss.js +33 -0
- package/dist/react/presentation/useDismiss.js.map +1 -0
- package/dist/react/presentation/useFocusTrap.d.ts +8 -0
- package/dist/react/presentation/useFocusTrap.js +35 -0
- package/dist/react/presentation/useFocusTrap.js.map +1 -0
- package/dist/react/presentation/useScrollLock.d.ts +6 -0
- package/dist/react/presentation/useScrollLock.js +18 -0
- package/dist/react/presentation/useScrollLock.js.map +1 -0
- package/dist/react/primitives/Checkbox.d.ts +28 -0
- package/dist/react/primitives/Checkbox.js +21 -0
- package/dist/react/primitives/Checkbox.js.map +1 -0
- package/dist/react/primitives/FieldShell.d.ts +29 -0
- package/dist/react/primitives/FieldShell.js +44 -0
- package/dist/react/primitives/FieldShell.js.map +1 -0
- package/dist/react/primitives/Input.d.ts +30 -0
- package/dist/react/primitives/Input.js +21 -0
- package/dist/react/primitives/Input.js.map +1 -0
- package/dist/react/primitives/Select.d.ts +34 -0
- package/dist/react/primitives/Select.js +26 -0
- package/dist/react/primitives/Select.js.map +1 -0
- package/dist/react/primitives/Textarea.d.ts +28 -0
- package/dist/react/primitives/Textarea.js +20 -0
- package/dist/react/primitives/Textarea.js.map +1 -0
- package/dist/react/recall.js +20 -0
- package/dist/react/recall.js.map +1 -0
- package/dist/react/registry.d.ts +16 -0
- package/dist/react/registry.js +16 -0
- package/dist/react/registry.js.map +1 -0
- package/dist/react/renderers/calculation.js +27 -0
- package/dist/react/renderers/calculation.js.map +1 -0
- package/dist/react/renderers/checkbox.js +32 -0
- package/dist/react/renderers/checkbox.js.map +1 -0
- package/dist/react/renderers/consent.js +97 -0
- package/dist/react/renderers/consent.js.map +1 -0
- package/dist/react/renderers/date.js +33 -0
- package/dist/react/renderers/date.js.map +1 -0
- package/dist/react/renderers/email.js +34 -0
- package/dist/react/renderers/email.js.map +1 -0
- package/dist/react/renderers/file.js +107 -0
- package/dist/react/renderers/file.js.map +1 -0
- package/dist/react/renderers/index.d.ts +8 -0
- package/dist/react/renderers/index.js +35 -0
- package/dist/react/renderers/index.js.map +1 -0
- package/dist/react/renderers/message.js +41 -0
- package/dist/react/renderers/message.js.map +1 -0
- package/dist/react/renderers/number.js +37 -0
- package/dist/react/renderers/number.js.map +1 -0
- package/dist/react/renderers/region.js +43 -0
- package/dist/react/renderers/region.js.map +1 -0
- package/dist/react/renderers/repeater.js +131 -0
- package/dist/react/renderers/repeater.js.map +1 -0
- package/dist/react/renderers/select.js +35 -0
- package/dist/react/renderers/select.js.map +1 -0
- package/dist/react/renderers/text.js +34 -0
- package/dist/react/renderers/text.js.map +1 -0
- package/dist/react/renderers/textarea.js +33 -0
- package/dist/react/renderers/textarea.js.map +1 -0
- package/dist/react/resolveForm.js +16 -0
- package/dist/react/resolveForm.js.map +1 -0
- package/dist/react/state.d.ts +52 -0
- package/dist/react/state.js +125 -0
- package/dist/react/state.js.map +1 -0
- package/dist/react/submitForm.d.ts +31 -0
- package/dist/react/submitForm.js +58 -0
- package/dist/react/submitForm.js.map +1 -0
- package/dist/react/uploadFile.d.ts +23 -0
- package/dist/react/uploadFile.js +39 -0
- package/dist/react/uploadFile.js.map +1 -0
- package/dist/react/useField.d.ts +13 -0
- package/dist/react/useField.js +49 -0
- package/dist/react/useField.js.map +1 -0
- package/dist/react/useFormState.d.ts +8 -0
- package/dist/react/useFormState.js +9 -0
- package/dist/react/useFormState.js.map +1 -0
- package/dist/react/useFormStep.d.ts +8 -0
- package/dist/react/useFormStep.js +9 -0
- package/dist/react/useFormStep.js.map +1 -0
- package/dist/react/validateField.js +27 -0
- package/dist/react/validateField.js.map +1 -0
- package/dist/recall/interpolate.d.ts +11 -0
- package/dist/recall/interpolate.js +17 -0
- package/dist/recall/interpolate.js.map +1 -0
- package/dist/recall/resolver.d.ts +22 -0
- package/dist/recall/resolver.js +48 -0
- package/dist/recall/resolver.js.map +1 -0
- package/dist/spam/captcha.d.ts +13 -0
- package/dist/spam/captcha.js +12 -0
- package/dist/spam/captcha.js.map +1 -0
- package/dist/spam/clientIp.js +9 -0
- package/dist/spam/clientIp.js.map +1 -0
- package/dist/spam/constants.d.ts +12 -0
- package/dist/spam/constants.js +21 -0
- package/dist/spam/constants.js.map +1 -0
- package/dist/spam/identify.d.ts +12 -0
- package/dist/spam/identify.js +17 -0
- package/dist/spam/identify.js.map +1 -0
- 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/rateLimiter.d.ts +18 -0
- package/dist/spam/rateLimiter.js +41 -0
- package/dist/spam/rateLimiter.js.map +1 -0
- package/dist/spam/reserved.js +30 -0
- package/dist/spam/reserved.js.map +1 -0
- package/dist/spam/resolveSpam.d.ts +12 -0
- package/dist/spam/resolveSpam.js +43 -0
- package/dist/spam/resolveSpam.js.map +1 -0
- package/dist/spam/spamGuard.js +81 -0
- package/dist/spam/spamGuard.js.map +1 -0
- package/dist/spam/types.d.ts +88 -0
- package/dist/spam/uploadHooks.js +33 -0
- package/dist/spam/uploadHooks.js.map +1 -0
- package/dist/submissions/SubmissionAnswers.d.ts +14 -0
- package/dist/submissions/SubmissionAnswers.js +137 -0
- package/dist/submissions/SubmissionAnswers.js.map +1 -0
- package/dist/submissions/SubmissionAnswersClient.js +178 -0
- package/dist/submissions/SubmissionAnswersClient.js.map +1 -0
- 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/runSubmission.d.ts +10 -0
- package/dist/submissions/runSubmission.js +306 -0
- package/dist/submissions/runSubmission.js.map +1 -0
- package/dist/submissions/types.d.ts +41 -0
- package/dist/submissions/validateSubmission.js +110 -0
- package/dist/submissions/validateSubmission.js.map +1 -0
- package/dist/submissions/votedCookie.d.ts +13 -0
- package/dist/submissions/votedCookie.js +27 -0
- package/dist/submissions/votedCookie.js.map +1 -0
- package/dist/translations/clientRuntimeKeys.d.ts +17 -0
- package/dist/translations/clientRuntimeKeys.js +46 -0
- package/dist/translations/clientRuntimeKeys.js.map +1 -0
- package/dist/translations/de.d.ts +12 -0
- package/dist/translations/de.js +309 -0
- package/dist/translations/de.js.map +1 -0
- package/dist/translations/en.d.ts +12 -0
- package/dist/translations/en.js +309 -0
- package/dist/translations/en.js.map +1 -0
- package/dist/translations/index.d.ts +23 -0
- package/dist/translations/index.js +37 -0
- package/dist/translations/index.js.map +1 -0
- package/dist/translations/keys.d.ts +308 -0
- package/dist/translations/keys.js +308 -0
- package/dist/translations/keys.js.map +1 -0
- package/dist/translations/makeTranslate.d.ts +15 -0
- package/dist/translations/makeTranslate.js +20 -0
- package/dist/translations/makeTranslate.js.map +1 -0
- package/dist/translations/server.js +32 -0
- package/dist/translations/server.js.map +1 -0
- package/dist/translations/useTranslation.js +12 -0
- package/dist/translations/useTranslation.js.map +1 -0
- package/dist/uploads/captureFileRef.d.ts +34 -0
- package/dist/uploads/captureFileRef.js +43 -0
- package/dist/uploads/captureFileRef.js.map +1 -0
- 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.d.ts +26 -0
- package/dist/uploads/resolveFileRef.js +45 -0
- package/dist/uploads/resolveFileRef.js.map +1 -0
- package/dist/uploads/types.d.ts +19 -0
- package/dist/validation/buildRuleBlocks.js +47 -0
- package/dist/validation/buildRuleBlocks.js.map +1 -0
- package/dist/validation/builtin/email.js +20 -0
- package/dist/validation/builtin/email.js.map +1 -0
- package/dist/validation/builtin/index.d.ts +14 -0
- package/dist/validation/builtin/index.js +38 -0
- package/dist/validation/builtin/index.js.map +1 -0
- package/dist/validation/builtin/matchesField.js +16 -0
- package/dist/validation/builtin/matchesField.js.map +1 -0
- package/dist/validation/builtin/max.js +22 -0
- package/dist/validation/builtin/max.js.map +1 -0
- package/dist/validation/builtin/maxDate.js +24 -0
- package/dist/validation/builtin/maxDate.js.map +1 -0
- package/dist/validation/builtin/maxLength.js +27 -0
- package/dist/validation/builtin/maxLength.js.map +1 -0
- package/dist/validation/builtin/min.js +22 -0
- package/dist/validation/builtin/min.js.map +1 -0
- package/dist/validation/builtin/minDate.js +24 -0
- package/dist/validation/builtin/minDate.js.map +1 -0
- package/dist/validation/builtin/minLength.js +27 -0
- package/dist/validation/builtin/minLength.js.map +1 -0
- package/dist/validation/builtin/notAlreadySubmitted.js +34 -0
- package/dist/validation/builtin/notAlreadySubmitted.js.map +1 -0
- package/dist/validation/builtin/oneOf.js +35 -0
- package/dist/validation/builtin/oneOf.js.map +1 -0
- package/dist/validation/builtin/pattern.js +50 -0
- package/dist/validation/builtin/pattern.js.map +1 -0
- package/dist/validation/builtin/url.js +23 -0
- package/dist/validation/builtin/url.js.map +1 -0
- 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/defineValidationRule.d.ts +12 -0
- package/dist/validation/defineValidationRule.js +11 -0
- package/dist/validation/defineValidationRule.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/message.js +14 -0
- package/dist/validation/message.js.map +1 -0
- package/dist/validation/registry.d.ts +11 -0
- package/dist/validation/registry.js +13 -0
- package/dist/validation/registry.js.map +1 -0
- package/dist/validation/runValidation.js +90 -0
- package/dist/validation/runValidation.js.map +1 -0
- package/dist/validation/types.d.ts +70 -0
- package/dist/validation/validateUrl.js +22 -0
- package/dist/validation/validateUrl.js.map +1 -0
- package/package.json +12 -7
- package/styles/form-builder.css +21 -11
- package/dist/constants-B-BUfetP.d.ts +0 -288
- package/dist/exports/client.js.map +0 -1
- package/dist/exports/react.js.map +0 -1
- package/dist/exports/rsc.js.map +0 -1
- package/dist/fieldTypes-B8jkNRob.d.ts +0 -188
- package/dist/fieldTypes-CzhhJXjg.js.map +0 -1
- package/dist/index-DfFYGbVF.d.ts +0 -481
- package/dist/keys-N5xGiUsh.js +0 -132
- package/dist/keys-N5xGiUsh.js.map +0 -1
- package/dist/registry-CoCyhtvB.js +0 -282
- package/dist/registry-CoCyhtvB.js.map +0 -1
- package/dist/resolver-OeQyVH2N.js +0 -665
- package/dist/resolver-OeQyVH2N.js.map +0 -1
- package/dist/translations-edMqq_2e.js +0 -152
- package/dist/translations-edMqq_2e.js.map +0 -1
- package/dist/types-DsJ_6kGJ.d.ts +0 -23
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { makeRenderBody } from "./body/serializeBody.js";
|
|
2
|
+
//#region src/actions/runActions.ts
|
|
3
|
+
/**
|
|
4
|
+
* Run each configured action, isolating failures: a throwing action is captured as a failed result
|
|
5
|
+
* and never breaks the others or the caller.
|
|
6
|
+
*/
|
|
7
|
+
const runActions = async (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
|
+
});
|
|
16
|
+
const results = [];
|
|
17
|
+
for (const instance of actions) {
|
|
18
|
+
const definition = registry.get(instance.blockType);
|
|
19
|
+
if (!definition) continue;
|
|
20
|
+
try {
|
|
21
|
+
await definition.run({
|
|
22
|
+
...ctx,
|
|
23
|
+
renderBody,
|
|
24
|
+
config: instance
|
|
25
|
+
});
|
|
26
|
+
results.push({
|
|
27
|
+
type: instance.blockType,
|
|
28
|
+
ok: true
|
|
29
|
+
});
|
|
30
|
+
} catch (error) {
|
|
31
|
+
results.push({
|
|
32
|
+
type: instance.blockType,
|
|
33
|
+
ok: false,
|
|
34
|
+
error: error instanceof Error ? error.message : String(error)
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return results;
|
|
39
|
+
};
|
|
40
|
+
//#endregion
|
|
41
|
+
export { runActions };
|
|
42
|
+
|
|
43
|
+
//# sourceMappingURL=runActions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
//#region src/actions/sign.d.ts
|
|
2
|
+
declare const SIGNATURE_HEADER = "X-Form-Signature";
|
|
3
|
+
/** HMAC-SHA256 over `body`, hex-encoded, wrapped in the versioned header value (`v1=<hex>`). */
|
|
4
|
+
declare const signPayload: (body: string, secret: string) => string;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { SIGNATURE_HEADER, signPayload };
|
|
7
|
+
//# sourceMappingURL=sign.d.ts.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { createHmac } from "node:crypto";
|
|
2
|
+
//#region src/actions/sign.ts
|
|
3
|
+
const SIGNATURE_HEADER = "X-Form-Signature";
|
|
4
|
+
/** HMAC-SHA256 over `body`, hex-encoded, wrapped in the versioned header value (`v1=<hex>`). */
|
|
5
|
+
const signPayload = (body, secret) => `v1=${createHmac("sha256", secret).update(body).digest("hex")}`;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { SIGNATURE_HEADER, signPayload };
|
|
8
|
+
|
|
9
|
+
//# sourceMappingURL=sign.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sign.js","names":[],"sources":["../../src/actions/sign.ts"],"sourcesContent":["import { createHmac } from 'node:crypto'\n\n// Distinct from @10x-media/webhooks' `X-Webhook-Signature` (which signs `timestamp.body`): this action signs the body alone, so a separate header prevents a consumer mistaking the two verification schemes.\nexport const SIGNATURE_HEADER = 'X-Form-Signature'\n\n/** HMAC-SHA256 over `body`, hex-encoded, wrapped in the versioned header value (`v1=<hex>`). */\nexport const signPayload = (body: string, secret: string): string =>\n\t`v1=${createHmac('sha256', secret).update(body).digest('hex')}`\n"],"mappings":";;AAGA,MAAa,mBAAmB;;AAGhC,MAAa,eAAe,MAAc,WACzC,MAAM,WAAW,UAAU,MAAM,EAAE,OAAO,IAAI,EAAE,OAAO,KAAK"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { asFieldTranslate } from "../translations/server.js";
|
|
2
|
+
import { FORMS_SLUG } from "../collections/forms.js";
|
|
3
|
+
import { FORM_SUBMISSIONS_SLUG } from "../collections/formSubmissions.js";
|
|
4
|
+
import { runActions } from "./runActions.js";
|
|
5
|
+
//#region src/actions/task.ts
|
|
6
|
+
const ACTIONS_TASK_SLUG = "form-builder-actions";
|
|
7
|
+
const asActions = (value) => Array.isArray(value) ? value : [];
|
|
8
|
+
const asValues = (value) => Array.isArray(value) ? value : [];
|
|
9
|
+
const asDescriptors = (value) => Array.isArray(value) ? value : [];
|
|
10
|
+
/**
|
|
11
|
+
* Load the form and submission by id and run the form's actions through the shared, failure-isolating
|
|
12
|
+
* `runActions`. Tolerates a missing form or submission (the row may have been deleted between enqueue and
|
|
13
|
+
* run) by returning early. Used by both the queued task handler and the inline fallback.
|
|
14
|
+
*/
|
|
15
|
+
const runActionsForSubmission = async (args) => {
|
|
16
|
+
const { input, registry, payload, req, richText } = args;
|
|
17
|
+
const submission = await payload.findByID({
|
|
18
|
+
collection: FORM_SUBMISSIONS_SLUG,
|
|
19
|
+
id: input.submissionId,
|
|
20
|
+
depth: 0,
|
|
21
|
+
overrideAccess: true,
|
|
22
|
+
req
|
|
23
|
+
}).catch(() => null);
|
|
24
|
+
if (!submission) return [];
|
|
25
|
+
const locale = typeof submission.locale === "string" ? submission.locale : req?.locale ?? "en";
|
|
26
|
+
const form = await payload.findByID({
|
|
27
|
+
collection: FORMS_SLUG,
|
|
28
|
+
id: input.formId,
|
|
29
|
+
depth: 0,
|
|
30
|
+
overrideAccess: true,
|
|
31
|
+
locale,
|
|
32
|
+
req
|
|
33
|
+
}).catch(() => null);
|
|
34
|
+
if (!form) return [];
|
|
35
|
+
const t = asFieldTranslate(req?.i18n?.t ?? ((key) => key));
|
|
36
|
+
const results = await runActions({
|
|
37
|
+
actions: asActions(form.actions),
|
|
38
|
+
registry,
|
|
39
|
+
richText,
|
|
40
|
+
form: {
|
|
41
|
+
id: form.id,
|
|
42
|
+
title: typeof form.title === "string" ? form.title : void 0
|
|
43
|
+
},
|
|
44
|
+
submissionId: submission.id,
|
|
45
|
+
values: asValues(submission.values),
|
|
46
|
+
descriptors: asDescriptors(submission.descriptors),
|
|
47
|
+
payload,
|
|
48
|
+
req,
|
|
49
|
+
locale,
|
|
50
|
+
t
|
|
51
|
+
});
|
|
52
|
+
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"}`);
|
|
53
|
+
return results;
|
|
54
|
+
};
|
|
55
|
+
/** Native Payload jobs task that runs a submission's post-submit actions out of band. */
|
|
56
|
+
const buildActionsTask = (registry, richText) => ({
|
|
57
|
+
slug: ACTIONS_TASK_SLUG,
|
|
58
|
+
inputSchema: [{
|
|
59
|
+
name: "formId",
|
|
60
|
+
type: "text",
|
|
61
|
+
required: true
|
|
62
|
+
}, {
|
|
63
|
+
name: "submissionId",
|
|
64
|
+
type: "text",
|
|
65
|
+
required: true
|
|
66
|
+
}],
|
|
67
|
+
handler: async ({ input, req }) => {
|
|
68
|
+
await runActionsForSubmission({
|
|
69
|
+
input,
|
|
70
|
+
registry,
|
|
71
|
+
payload: req.payload,
|
|
72
|
+
req,
|
|
73
|
+
richText
|
|
74
|
+
});
|
|
75
|
+
return { output: {} };
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
/** Register the actions task on `config.jobs.tasks`, creating the jobs config if absent. */
|
|
79
|
+
const registerActionsTask = (config, registry, richText) => {
|
|
80
|
+
config.jobs ??= {};
|
|
81
|
+
config.jobs.tasks ??= [];
|
|
82
|
+
config.jobs.tasks.push(buildActionsTask(registry, richText));
|
|
83
|
+
};
|
|
84
|
+
//#endregion
|
|
85
|
+
export { ACTIONS_TASK_SLUG, registerActionsTask, runActionsForSubmission };
|
|
86
|
+
|
|
87
|
+
//# sourceMappingURL=task.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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 { 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 } 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. */\nexport type ActionsTaskInput = { formId: number | string; submissionId: number | string }\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/**\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 results = await runActions({\n\t\tactions: asActions(form.actions),\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\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\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":";;;;;AAWA,MAAa,oBAAoB;AAKjC,MAAM,aAAa,UAClB,MAAM,QAAQ,KAAK,IAAK,QAA6B,CAAC;AAEvD,MAAM,YAAY,UACjB,MAAM,QAAQ,KAAK,IAAK,QAA8B,CAAC;AAExD,MAAM,iBAAiB,UACtB,MAAM,QAAQ,KAAK,IAAK,QAAmC,CAAC;;;;;;AAO7D,MAAa,0BAA0B,OAAO,SAMf;CAC9B,MAAM,EAAE,OAAO,UAAU,SAAS,KAAK,aAAa;CACpD,MAAM,aAAa,MAAM,QACvB,SAAS;EACT,YAAY;EACZ,IAAI,MAAM;EACV,OAAO;EACP,gBAAgB;EAChB;CACD,CAAC,EACA,YAAY,IAAI;CAClB,IAAI,CAAC,YACJ,OAAO,CAAC;CAOT,MAAM,SAAS,OAAO,WAAW,WAAW,WAAW,WAAW,SAAU,KAAK,UAAU;CAE3F,MAAM,OAAO,MAAM,QACjB,SAAS;EACT,YAAY;EACZ,IAAI,MAAM;EACV,OAAO;EACP,gBAAgB;EAIR;EACR;CACD,CAAC,EACA,YAAY,IAAI;CAClB,IAAI,CAAC,MACJ,OAAO,CAAC;CAGT,MAAM,IAAe,iBAAiB,KAAK,MAAM,OAAO,QAAgB,IAAI;CAE5E,MAAM,UAAU,MAAM,WAAW;EAChC,SAAS,UAAU,KAAK,OAAO;EAC/B;EACA;EACA,MAAM;GAAE,IAAI,KAAK;GAAI,OAAO,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ,KAAA;EAAU;EACpF,cAAc,WAAW;EACzB,QAAQ,SAAS,WAAW,MAAM;EAClC,aAAa,cAAc,WAAW,WAAW;EACjD;EACA;EACA;EACA;CACD,CAAC;CAGD,KAAK,MAAM,UAAU,SACpB,IAAI,CAAC,OAAO,IACX,QAAQ,QAAQ,MACf,oCAAoC,OAAO,KAAK,0BAA0B,OAAO,WAAW,EAAE,EAAE,IAAI,OAAO,SAAS,iBACrH;CAGF,OAAO;AACR;;AAGA,MAAa,oBACZ,UACA,cAEC;CACA,MAAM;CACN,aAAa,CACZ;EAAE,MAAM;EAAU,MAAM;EAAQ,UAAU;CAAK,GAC/C;EAAE,MAAM;EAAgB,MAAM;EAAQ,UAAU;CAAK,CACtD;CACA,SAAS,OAAO,EAAE,OAAO,UAAU;EAClC,MAAM,wBAAwB;GACtB;GACP;GACA,SAAS,IAAI;GACb;GACA;EACD,CAAC;EACD,OAAO,EAAE,QAAQ,CAAC,EAAE;CACrB;AACD;;AAGD,MAAa,uBACZ,QACA,UACA,aACU;CACV,OAAO,SAAS,CAAC;CACjB,OAAO,KAAK,UAAU,CAAC;CACvB,OAAO,KAAK,MAAM,KAAK,iBAAiB,UAAU,QAAQ,CAAC;AAC5D"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { FormFieldInstance } from "../submissions/types.js";
|
|
2
|
+
import { FieldAggregation, SubmissionStatusFilter } from "./types.js";
|
|
3
|
+
import { Payload, PayloadRequest } from "payload";
|
|
4
|
+
|
|
5
|
+
//#region src/aggregation/aggregateResponses.d.ts
|
|
6
|
+
type AggregateFormResponsesArgs = {
|
|
7
|
+
payload: Payload;
|
|
8
|
+
formId: number | string; /** Field machine names to aggregate. Defaults to every field that declares options (enumerable). */
|
|
9
|
+
fields?: string[]; /** Submission status to count. Defaults to `complete`. */
|
|
10
|
+
status?: SubmissionStatusFilter;
|
|
11
|
+
req?: PayloadRequest; /** Safety cap on submissions scanned; beyond it the result is flagged `truncated`. Default 10000. */
|
|
12
|
+
maxSubmissions?: number; /** Page size for the submission scan. Default 500. */
|
|
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
|
+
}[]>;
|
|
23
|
+
};
|
|
24
|
+
/** True when a field declares non-empty options (a choice field safe to aggregate publicly). */
|
|
25
|
+
declare const fieldHasOptions: (field: FormFieldInstance) => boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Aggregate submission responses for a form. Loads the form for current labels + option order, then pages
|
|
28
|
+
* `payload.find` over its submissions (JSON `values` cannot be GROUP BY'd portably across Mongo + Postgres,
|
|
29
|
+
* so we scan + reduce in JS, cross-DB-identically). Bounded by `maxSubmissions`; beyond it the result is
|
|
30
|
+
* `truncated`. Returns one `FieldAggregation` per requested field (or per enumerable field by default).
|
|
31
|
+
*/
|
|
32
|
+
declare const aggregateFormResponses: (args: AggregateFormResponsesArgs) => Promise<FieldAggregation[]>;
|
|
33
|
+
type AggregateFieldResponsesArgs = Omit<AggregateFormResponsesArgs, 'fields'> & {
|
|
34
|
+
field: string;
|
|
35
|
+
};
|
|
36
|
+
/** Single-field convenience over `aggregateFormResponses`. Returns the field's aggregation, or null if absent. */
|
|
37
|
+
declare const aggregateFieldResponses: (args: AggregateFieldResponsesArgs) => Promise<FieldAggregation | null>;
|
|
38
|
+
//#endregion
|
|
39
|
+
export { AggregateFieldResponsesArgs, AggregateFormResponsesArgs, aggregateFieldResponses, aggregateFormResponses, fieldHasOptions };
|
|
40
|
+
//# sourceMappingURL=aggregateResponses.d.ts.map
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { isNamedField } from "../fields/fieldKey.js";
|
|
2
|
+
import { instanceOptionsOf } from "../fields/instanceOptions.js";
|
|
3
|
+
import { aggregateRowsForFields } from "./aggregateRows.js";
|
|
4
|
+
import { FORMS_SLUG } from "../collections/forms.js";
|
|
5
|
+
import { FORM_SUBMISSIONS_SLUG } from "../collections/formSubmissions.js";
|
|
6
|
+
//#region src/aggregation/aggregateResponses.ts
|
|
7
|
+
/** True when a field declares non-empty options (a choice field safe to aggregate publicly). */
|
|
8
|
+
const fieldHasOptions = (field) => instanceOptionsOf(field) !== void 0;
|
|
9
|
+
const metaFor = (field, resolved) => ({
|
|
10
|
+
field: field.name,
|
|
11
|
+
label: field.label ?? field.name,
|
|
12
|
+
fieldType: field.blockType,
|
|
13
|
+
options: resolved && resolved.length > 0 ? resolved : instanceOptionsOf(field)
|
|
14
|
+
});
|
|
15
|
+
/**
|
|
16
|
+
* Aggregate submission responses for a form. Loads the form for current labels + option order, then pages
|
|
17
|
+
* `payload.find` over its submissions (JSON `values` cannot be GROUP BY'd portably across Mongo + Postgres,
|
|
18
|
+
* so we scan + reduce in JS, cross-DB-identically). Bounded by `maxSubmissions`; beyond it the result is
|
|
19
|
+
* `truncated`. Returns one `FieldAggregation` per requested field (or per enumerable field by default).
|
|
20
|
+
*/
|
|
21
|
+
const aggregateFormResponses = async (args) => {
|
|
22
|
+
const { payload, formId, fields, status = "complete", req, maxSubmissions = 1e4, pageSize = 500, resolvedOptions } = args;
|
|
23
|
+
const form = await payload.findByID({
|
|
24
|
+
collection: FORMS_SLUG,
|
|
25
|
+
id: formId,
|
|
26
|
+
depth: 0,
|
|
27
|
+
overrideAccess: true,
|
|
28
|
+
req
|
|
29
|
+
}).catch(() => null);
|
|
30
|
+
if (!form || !Array.isArray(form.fields)) return [];
|
|
31
|
+
const instances = form.fields;
|
|
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));
|
|
33
|
+
if (selected.length === 0) return [];
|
|
34
|
+
const metas = selected.map((field) => metaFor(field, resolvedOptions?.[field.name]));
|
|
35
|
+
const where = { and: [{ form: { equals: formId } }, ...status === "all" ? [] : [{ status: { equals: status } }]] };
|
|
36
|
+
const rows = [];
|
|
37
|
+
let page = 1;
|
|
38
|
+
let truncated = false;
|
|
39
|
+
for (;;) {
|
|
40
|
+
const result = await payload.find({
|
|
41
|
+
collection: FORM_SUBMISSIONS_SLUG,
|
|
42
|
+
where,
|
|
43
|
+
limit: pageSize,
|
|
44
|
+
page,
|
|
45
|
+
depth: 0,
|
|
46
|
+
overrideAccess: true,
|
|
47
|
+
req,
|
|
48
|
+
select: {
|
|
49
|
+
values: true,
|
|
50
|
+
descriptors: true
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
for (const doc of result.docs) {
|
|
54
|
+
if (rows.length >= maxSubmissions) {
|
|
55
|
+
truncated = true;
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
rows.push({
|
|
59
|
+
values: doc.values,
|
|
60
|
+
descriptors: doc.descriptors
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
if (truncated || !result.hasNextPage) break;
|
|
64
|
+
page += 1;
|
|
65
|
+
}
|
|
66
|
+
return aggregateRowsForFields(rows, metas, truncated);
|
|
67
|
+
};
|
|
68
|
+
/** Single-field convenience over `aggregateFormResponses`. Returns the field's aggregation, or null if absent. */
|
|
69
|
+
const aggregateFieldResponses = async (args) => {
|
|
70
|
+
const { field, ...rest } = args;
|
|
71
|
+
const [result] = await aggregateFormResponses({
|
|
72
|
+
...rest,
|
|
73
|
+
fields: [field]
|
|
74
|
+
});
|
|
75
|
+
return result ?? null;
|
|
76
|
+
};
|
|
77
|
+
//#endregion
|
|
78
|
+
export { aggregateFieldResponses, aggregateFormResponses, fieldHasOptions };
|
|
79
|
+
|
|
80
|
+
//# sourceMappingURL=aggregateResponses.js.map
|
|
@@ -0,0 +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 { 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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { AggregationRow, FieldAggregation, FieldMeta } from "./types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/aggregation/aggregateRows.d.ts
|
|
4
|
+
/** Aggregate one field across rows: respondent-denominated counts + percentages, option-ordered. */
|
|
5
|
+
declare const aggregateRowForField: (rows: AggregationRow[], meta: FieldMeta, truncated: boolean) => FieldAggregation;
|
|
6
|
+
/** Aggregate several fields across the same set of rows. */
|
|
7
|
+
declare const aggregateRowsForFields: (rows: AggregationRow[], metas: FieldMeta[], truncated: boolean) => FieldAggregation[];
|
|
8
|
+
//#endregion
|
|
9
|
+
export { aggregateRowForField, aggregateRowsForFields };
|
|
10
|
+
//# sourceMappingURL=aggregateRows.d.ts.map
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
//#region src/aggregation/aggregateRows.ts
|
|
2
|
+
const round1 = (n) => Math.round(n * 10) / 10;
|
|
3
|
+
/** The grouping keys a single answer contributes: [] for empty, one per element for arrays, else one. */
|
|
4
|
+
const valueKeys = (value) => {
|
|
5
|
+
if (value == null || value === "") return [];
|
|
6
|
+
if (Array.isArray(value)) return value.filter((entry) => entry != null && entry !== "").map((entry) => String(entry));
|
|
7
|
+
return [String(value)];
|
|
8
|
+
};
|
|
9
|
+
/** Merge every row's descriptor snapshot for one field into a value -> label map (last write wins). */
|
|
10
|
+
const snapshotLabels = (rows, field) => {
|
|
11
|
+
const labels = /* @__PURE__ */ new Map();
|
|
12
|
+
for (const row of rows) for (const descriptor of row.descriptors ?? []) if (descriptor.field === field && descriptor.optionLabels) for (const [value, label] of Object.entries(descriptor.optionLabels)) labels.set(value, label);
|
|
13
|
+
return labels;
|
|
14
|
+
};
|
|
15
|
+
/** Aggregate one field across rows: respondent-denominated counts + percentages, option-ordered. */
|
|
16
|
+
const aggregateRowForField = (rows, meta, truncated) => {
|
|
17
|
+
const counts = /* @__PURE__ */ new Map();
|
|
18
|
+
const order = [];
|
|
19
|
+
for (const option of meta.options ?? []) {
|
|
20
|
+
counts.set(option.value, 0);
|
|
21
|
+
order.push(option.value);
|
|
22
|
+
}
|
|
23
|
+
const snapshots = snapshotLabels(rows, meta.field);
|
|
24
|
+
let total = 0;
|
|
25
|
+
for (const row of rows) {
|
|
26
|
+
const keys = valueKeys((row.values ?? []).find((entry) => entry.field === meta.field)?.value);
|
|
27
|
+
if (keys.length === 0) continue;
|
|
28
|
+
total += 1;
|
|
29
|
+
for (const key of keys) {
|
|
30
|
+
if (!counts.has(key)) {
|
|
31
|
+
counts.set(key, 0);
|
|
32
|
+
order.push(key);
|
|
33
|
+
}
|
|
34
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const optionValues = new Set((meta.options ?? []).map((option) => option.value));
|
|
38
|
+
const leftovers = order.filter((value) => !optionValues.has(value)).sort((a, b) => (counts.get(b) ?? 0) - (counts.get(a) ?? 0));
|
|
39
|
+
const ordered = [...(meta.options ?? []).map((option) => option.value), ...leftovers];
|
|
40
|
+
const optionLabelByValue = new Map((meta.options ?? []).map((option) => [option.value, option.label]));
|
|
41
|
+
const buckets = ordered.map((value) => {
|
|
42
|
+
const count = counts.get(value) ?? 0;
|
|
43
|
+
return {
|
|
44
|
+
value,
|
|
45
|
+
label: optionLabelByValue.get(value) ?? snapshots.get(value) ?? value,
|
|
46
|
+
count,
|
|
47
|
+
percentage: total > 0 ? round1(count / total * 100) : 0
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
return {
|
|
51
|
+
field: meta.field,
|
|
52
|
+
label: meta.label,
|
|
53
|
+
fieldType: meta.fieldType,
|
|
54
|
+
total,
|
|
55
|
+
buckets,
|
|
56
|
+
truncated
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
/** Aggregate several fields across the same set of rows. */
|
|
60
|
+
const aggregateRowsForFields = (rows, metas, truncated) => metas.map((meta) => aggregateRowForField(rows, meta, truncated));
|
|
61
|
+
//#endregion
|
|
62
|
+
export { aggregateRowForField, aggregateRowsForFields };
|
|
63
|
+
|
|
64
|
+
//# sourceMappingURL=aggregateRows.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aggregateRows.js","names":[],"sources":["../../src/aggregation/aggregateRows.ts"],"sourcesContent":["import type { AggregationBucket, AggregationRow, FieldAggregation, FieldMeta } from './types'\n\nconst round1 = (n: number): number => Math.round(n * 10) / 10\n\n/** The grouping keys a single answer contributes: [] for empty, one per element for arrays, else one. */\nconst valueKeys = (value: unknown): string[] => {\n\tif (value == null || value === '') {\n\t\treturn []\n\t}\n\tif (Array.isArray(value)) {\n\t\treturn value.filter((entry) => entry != null && entry !== '').map((entry) => String(entry))\n\t}\n\treturn [String(value)]\n}\n\n/** Merge every row's descriptor snapshot for one field into a value -> label map (last write wins). */\nconst snapshotLabels = (rows: AggregationRow[], field: string): Map<string, string> => {\n\tconst labels = new Map<string, string>()\n\tfor (const row of rows) {\n\t\tfor (const descriptor of row.descriptors ?? []) {\n\t\t\tif (descriptor.field === field && descriptor.optionLabels) {\n\t\t\t\tfor (const [value, label] of Object.entries(descriptor.optionLabels)) {\n\t\t\t\t\tlabels.set(value, label)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn labels\n}\n\n/** Aggregate one field across rows: respondent-denominated counts + percentages, option-ordered. */\nexport const aggregateRowForField = (\n\trows: AggregationRow[],\n\tmeta: FieldMeta,\n\ttruncated: boolean\n): FieldAggregation => {\n\tconst counts = new Map<string, number>()\n\tconst order: string[] = []\n\tfor (const option of meta.options ?? []) {\n\t\tcounts.set(option.value, 0)\n\t\torder.push(option.value)\n\t}\n\tconst snapshots = snapshotLabels(rows, meta.field)\n\tlet total = 0\n\tfor (const row of rows) {\n\t\tconst answer = (row.values ?? []).find((entry) => entry.field === meta.field)\n\t\tconst keys = valueKeys(answer?.value)\n\t\tif (keys.length === 0) {\n\t\t\tcontinue\n\t\t}\n\t\ttotal += 1\n\t\tfor (const key of keys) {\n\t\t\tif (!counts.has(key)) {\n\t\t\t\tcounts.set(key, 0)\n\t\t\t\torder.push(key)\n\t\t\t}\n\t\t\tcounts.set(key, (counts.get(key) ?? 0) + 1)\n\t\t}\n\t}\n\n\tconst optionValues = new Set((meta.options ?? []).map((option) => option.value))\n\tconst leftovers = order\n\t\t.filter((value) => !optionValues.has(value))\n\t\t.sort((a, b) => (counts.get(b) ?? 0) - (counts.get(a) ?? 0))\n\tconst ordered = [...(meta.options ?? []).map((option) => option.value), ...leftovers]\n\n\tconst optionLabelByValue = new Map(\n\t\t(meta.options ?? []).map((option) => [option.value, option.label])\n\t)\n\tconst buckets: AggregationBucket[] = ordered.map((value) => {\n\t\tconst count = counts.get(value) ?? 0\n\t\treturn {\n\t\t\tvalue,\n\t\t\tlabel: optionLabelByValue.get(value) ?? snapshots.get(value) ?? value,\n\t\t\tcount,\n\t\t\tpercentage: total > 0 ? round1((count / total) * 100) : 0,\n\t\t}\n\t})\n\n\treturn {\n\t\tfield: meta.field,\n\t\tlabel: meta.label,\n\t\tfieldType: meta.fieldType,\n\t\ttotal,\n\t\tbuckets,\n\t\ttruncated,\n\t}\n}\n\n/** Aggregate several fields across the same set of rows. */\nexport const aggregateRowsForFields = (\n\trows: AggregationRow[],\n\tmetas: FieldMeta[],\n\ttruncated: boolean\n): FieldAggregation[] => metas.map((meta) => aggregateRowForField(rows, meta, truncated))\n"],"mappings":";AAEA,MAAM,UAAU,MAAsB,KAAK,MAAM,IAAI,EAAE,IAAI;;AAG3D,MAAM,aAAa,UAA6B;CAC/C,IAAI,SAAS,QAAQ,UAAU,IAC9B,OAAO,CAAC;CAET,IAAI,MAAM,QAAQ,KAAK,GACtB,OAAO,MAAM,QAAQ,UAAU,SAAS,QAAQ,UAAU,EAAE,EAAE,KAAK,UAAU,OAAO,KAAK,CAAC;CAE3F,OAAO,CAAC,OAAO,KAAK,CAAC;AACtB;;AAGA,MAAM,kBAAkB,MAAwB,UAAuC;CACtF,MAAM,yBAAS,IAAI,IAAoB;CACvC,KAAK,MAAM,OAAO,MACjB,KAAK,MAAM,cAAc,IAAI,eAAe,CAAC,GAC5C,IAAI,WAAW,UAAU,SAAS,WAAW,cAC5C,KAAK,MAAM,CAAC,OAAO,UAAU,OAAO,QAAQ,WAAW,YAAY,GAClE,OAAO,IAAI,OAAO,KAAK;CAK3B,OAAO;AACR;;AAGA,MAAa,wBACZ,MACA,MACA,cACsB;CACtB,MAAM,yBAAS,IAAI,IAAoB;CACvC,MAAM,QAAkB,CAAC;CACzB,KAAK,MAAM,UAAU,KAAK,WAAW,CAAC,GAAG;EACxC,OAAO,IAAI,OAAO,OAAO,CAAC;EAC1B,MAAM,KAAK,OAAO,KAAK;CACxB;CACA,MAAM,YAAY,eAAe,MAAM,KAAK,KAAK;CACjD,IAAI,QAAQ;CACZ,KAAK,MAAM,OAAO,MAAM;EAEvB,MAAM,OAAO,WADG,IAAI,UAAU,CAAC,GAAG,MAAM,UAAU,MAAM,UAAU,KAAK,KAC3C,GAAG,KAAK;EACpC,IAAI,KAAK,WAAW,GACnB;EAED,SAAS;EACT,KAAK,MAAM,OAAO,MAAM;GACvB,IAAI,CAAC,OAAO,IAAI,GAAG,GAAG;IACrB,OAAO,IAAI,KAAK,CAAC;IACjB,MAAM,KAAK,GAAG;GACf;GACA,OAAO,IAAI,MAAM,OAAO,IAAI,GAAG,KAAK,KAAK,CAAC;EAC3C;CACD;CAEA,MAAM,eAAe,IAAI,KAAK,KAAK,WAAW,CAAC,GAAG,KAAK,WAAW,OAAO,KAAK,CAAC;CAC/E,MAAM,YAAY,MAChB,QAAQ,UAAU,CAAC,aAAa,IAAI,KAAK,CAAC,EAC1C,MAAM,GAAG,OAAO,OAAO,IAAI,CAAC,KAAK,MAAM,OAAO,IAAI,CAAC,KAAK,EAAE;CAC5D,MAAM,UAAU,CAAC,IAAI,KAAK,WAAW,CAAC,GAAG,KAAK,WAAW,OAAO,KAAK,GAAG,GAAG,SAAS;CAEpF,MAAM,qBAAqB,IAAI,KAC7B,KAAK,WAAW,CAAC,GAAG,KAAK,WAAW,CAAC,OAAO,OAAO,OAAO,KAAK,CAAC,CAClE;CACA,MAAM,UAA+B,QAAQ,KAAK,UAAU;EAC3D,MAAM,QAAQ,OAAO,IAAI,KAAK,KAAK;EACnC,OAAO;GACN;GACA,OAAO,mBAAmB,IAAI,KAAK,KAAK,UAAU,IAAI,KAAK,KAAK;GAChE;GACA,YAAY,QAAQ,IAAI,OAAQ,QAAQ,QAAS,GAAG,IAAI;EACzD;CACD,CAAC;CAED,OAAO;EACN,OAAO,KAAK;EACZ,OAAO,KAAK;EACZ,WAAW,KAAK;EAChB;EACA;EACA;CACD;AACD;;AAGA,MAAa,0BACZ,MACA,OACA,cACwB,MAAM,KAAK,SAAS,qBAAqB,MAAM,MAAM,SAAS,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { FieldAggregation } from "./types.js";
|
|
2
|
+
import { Payload, PayloadRequest } from "payload";
|
|
3
|
+
|
|
4
|
+
//#region src/aggregation/resolveResultsRequest.d.ts
|
|
5
|
+
type FormResultsAccessArgs = {
|
|
6
|
+
req: PayloadRequest; /** The loaded forms document (depth 0). Untyped beyond `id`: hosts read their own fields (e.g. `form.tenant`). */
|
|
7
|
+
form: {
|
|
8
|
+
id: number | string;
|
|
9
|
+
} & Record<string, unknown>;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Host seam gating anonymous results reads, evaluated after the form is loaded and before anything
|
|
13
|
+
* is aggregated. Multi-tenant recipe: compare `form.tenant` against the tenant derived from `req`
|
|
14
|
+
* (host header, cookie, or auth context) and return `false` for a cross-tenant read. Authenticated
|
|
15
|
+
* callers bypass this seam (they are admin-trusted, like the rest of the results endpoint).
|
|
16
|
+
*/
|
|
17
|
+
type FormResultsAccess = (args: FormResultsAccessArgs) => boolean | Promise<boolean>;
|
|
18
|
+
type ResolveResultsRequestArgs = {
|
|
19
|
+
payload: Payload;
|
|
20
|
+
formId: number | string | undefined; /** The requested field (query param). */
|
|
21
|
+
field?: string; /** Whether the caller is authenticated (an admin/user). */
|
|
22
|
+
isAuthed: boolean;
|
|
23
|
+
req?: PayloadRequest; /** Optional host seam for anonymous reads; absent means plugin-default gating only. */
|
|
24
|
+
access?: FormResultsAccess;
|
|
25
|
+
/**
|
|
26
|
+
* Poll-eligible field types (registry-derived). When set, an anonymous read of an
|
|
27
|
+
* option-source poll also requires the results field's instance to be one of these types,
|
|
28
|
+
* so legacy or db-written docs pointing at a free-text field can never expose stored
|
|
29
|
+
* answers as leftover result buckets.
|
|
30
|
+
*/
|
|
31
|
+
eligibleTypes?: readonly string[];
|
|
32
|
+
};
|
|
33
|
+
type ResolveResultsRequestResult = {
|
|
34
|
+
status: number;
|
|
35
|
+
body: {
|
|
36
|
+
results: FieldAggregation[];
|
|
37
|
+
} | {
|
|
38
|
+
errors: {
|
|
39
|
+
message: string;
|
|
40
|
+
}[];
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Authorize and resolve a poll/survey results request. Authed callers may aggregate any field (or all
|
|
45
|
+
* enumerable fields) and bypass the `access` seam; for a poll-enabled form they also get the results
|
|
46
|
+
* field's effective options injected so sourced/resolver-backed polls render labels (best-effort, a
|
|
47
|
+
* resolve failure degrades to raw values rather than blocking the trusted read). Anonymous callers are
|
|
48
|
+
* allowed only when the form's poll is enabled, the poll's `resultsVisibility` permits it (`afterVote`:
|
|
49
|
+
* any time; `afterClose`: only once `closesAt` has passed), and the optional host `access` seam
|
|
50
|
+
* approves; and then only for the configured `poll.resultsField`, and only if that field is enumerable
|
|
51
|
+
* so a misconfigured `resultsField` pointing at a free-text or PII field can never be dumped publicly. A
|
|
52
|
+
* static poll is enumerable through its authored options; a poll whose options come from an
|
|
53
|
+
* `optionSource` or the field type's own `resolveOptions` resolves them here (registry via
|
|
54
|
+
* `config.custom`, per-request cache), gated by `eligibleTypes` and enumerable only when resolution
|
|
55
|
+
* yields any, with the resolved options driving bucket order and labels. Resolution failure fails
|
|
56
|
+
* closed (503) on the anonymous path. Returns only aggregate counts, never raw submissions.
|
|
57
|
+
*/
|
|
58
|
+
declare const resolveFormResultsRequest: (args: ResolveResultsRequestArgs) => Promise<ResolveResultsRequestResult>;
|
|
59
|
+
//#endregion
|
|
60
|
+
export { FormResultsAccess, FormResultsAccessArgs, ResolveResultsRequestArgs, ResolveResultsRequestResult, resolveFormResultsRequest };
|
|
61
|
+
//# sourceMappingURL=resolveResultsRequest.d.ts.map
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { isPollClosed, pollConfigOf } from "../form/pollState.js";
|
|
2
|
+
import { aggregateFormResponses, fieldHasOptions } from "./aggregateResponses.js";
|
|
3
|
+
import { resolvePollOutcome } from "../poll/resolvePollOutcome.js";
|
|
4
|
+
import { shouldAutoResolvePoll } from "../poll/closeJob.js";
|
|
5
|
+
import { resolveEffectivePollOptions } from "../poll/effectivePollOptions.js";
|
|
6
|
+
import { FORMS_SLUG } from "../collections/forms.js";
|
|
7
|
+
//#region src/aggregation/resolveResultsRequest.ts
|
|
8
|
+
const forbidden = {
|
|
9
|
+
status: 403,
|
|
10
|
+
body: { errors: [{ message: "Forbidden" }] }
|
|
11
|
+
};
|
|
12
|
+
const unavailable = {
|
|
13
|
+
status: 503,
|
|
14
|
+
body: { errors: [{ message: "Poll options unavailable" }] }
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Authorize and resolve a poll/survey results request. Authed callers may aggregate any field (or all
|
|
18
|
+
* enumerable fields) and bypass the `access` seam; for a poll-enabled form they also get the results
|
|
19
|
+
* field's effective options injected so sourced/resolver-backed polls render labels (best-effort, a
|
|
20
|
+
* resolve failure degrades to raw values rather than blocking the trusted read). Anonymous callers are
|
|
21
|
+
* allowed only when the form's poll is enabled, the poll's `resultsVisibility` permits it (`afterVote`:
|
|
22
|
+
* any time; `afterClose`: only once `closesAt` has passed), and the optional host `access` seam
|
|
23
|
+
* approves; and then only for the configured `poll.resultsField`, and only if that field is enumerable
|
|
24
|
+
* so a misconfigured `resultsField` pointing at a free-text or PII field can never be dumped publicly. A
|
|
25
|
+
* static poll is enumerable through its authored options; a poll whose options come from an
|
|
26
|
+
* `optionSource` or the field type's own `resolveOptions` resolves them here (registry via
|
|
27
|
+
* `config.custom`, per-request cache), gated by `eligibleTypes` and enumerable only when resolution
|
|
28
|
+
* yields any, with the resolved options driving bucket order and labels. Resolution failure fails
|
|
29
|
+
* closed (503) on the anonymous path. Returns only aggregate counts, never raw submissions.
|
|
30
|
+
*/
|
|
31
|
+
const resolveFormResultsRequest = async (args) => {
|
|
32
|
+
const { payload, formId, field, isAuthed, req, access, eligibleTypes } = args;
|
|
33
|
+
if (formId == null) return {
|
|
34
|
+
status: 400,
|
|
35
|
+
body: { errors: [{ message: "Missing form id" }] }
|
|
36
|
+
};
|
|
37
|
+
const form = await payload.findByID({
|
|
38
|
+
collection: FORMS_SLUG,
|
|
39
|
+
id: formId,
|
|
40
|
+
depth: 0,
|
|
41
|
+
overrideAccess: true,
|
|
42
|
+
req
|
|
43
|
+
}).catch(() => null);
|
|
44
|
+
if (!form) return {
|
|
45
|
+
status: 404,
|
|
46
|
+
body: { errors: [{ message: "Not found" }] }
|
|
47
|
+
};
|
|
48
|
+
if (shouldAutoResolvePoll(form)) await resolvePollOutcome({
|
|
49
|
+
payload,
|
|
50
|
+
formId,
|
|
51
|
+
req
|
|
52
|
+
}).catch(() => void 0);
|
|
53
|
+
let fields;
|
|
54
|
+
let resolvedOptions;
|
|
55
|
+
if (isAuthed) {
|
|
56
|
+
fields = field ? [field] : void 0;
|
|
57
|
+
const poll = pollConfigOf(form.poll);
|
|
58
|
+
const resultsField = typeof poll?.resultsField === "string" && poll.resultsField.length > 0 ? poll.resultsField : void 0;
|
|
59
|
+
if (form.pollEnabled === true && resultsField && (!field || field === resultsField)) {
|
|
60
|
+
const options = await resolveEffectivePollOptions({
|
|
61
|
+
payload,
|
|
62
|
+
req,
|
|
63
|
+
form
|
|
64
|
+
}).catch(() => []);
|
|
65
|
+
if (options.length > 0) resolvedOptions = { [resultsField]: options };
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
const poll = pollConfigOf(form.poll);
|
|
69
|
+
if (form.pollEnabled !== true || !poll) return forbidden;
|
|
70
|
+
if (poll.resultsVisibility === "afterClose" && !isPollClosed(poll)) return forbidden;
|
|
71
|
+
if (access) {
|
|
72
|
+
if (!(req ? await access({
|
|
73
|
+
req,
|
|
74
|
+
form
|
|
75
|
+
}) : false)) return forbidden;
|
|
76
|
+
}
|
|
77
|
+
const publicField = typeof poll.resultsField === "string" && poll.resultsField.length > 0 ? poll.resultsField : void 0;
|
|
78
|
+
if (!publicField) return forbidden;
|
|
79
|
+
if (field && field !== publicField) return forbidden;
|
|
80
|
+
const instance = (Array.isArray(form.fields) ? form.fields : []).find((entry) => entry.name === publicField);
|
|
81
|
+
if (!instance) return forbidden;
|
|
82
|
+
const hasSource = typeof poll.optionSource === "string" && poll.optionSource.length > 0;
|
|
83
|
+
const authored = fieldHasOptions(instance);
|
|
84
|
+
if ((hasSource || !authored) && eligibleTypes && !eligibleTypes.includes(instance.blockType)) return forbidden;
|
|
85
|
+
let options;
|
|
86
|
+
try {
|
|
87
|
+
options = await resolveEffectivePollOptions({
|
|
88
|
+
payload,
|
|
89
|
+
req,
|
|
90
|
+
form
|
|
91
|
+
});
|
|
92
|
+
} catch {
|
|
93
|
+
return unavailable;
|
|
94
|
+
}
|
|
95
|
+
if (options.length === 0) return forbidden;
|
|
96
|
+
resolvedOptions = { [publicField]: options };
|
|
97
|
+
fields = [publicField];
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
status: 200,
|
|
101
|
+
body: { results: await aggregateFormResponses({
|
|
102
|
+
payload,
|
|
103
|
+
formId,
|
|
104
|
+
fields,
|
|
105
|
+
req,
|
|
106
|
+
resolvedOptions
|
|
107
|
+
}) }
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
//#endregion
|
|
111
|
+
export { resolveFormResultsRequest };
|
|
112
|
+
|
|
113
|
+
//# sourceMappingURL=resolveResultsRequest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolveResultsRequest.js","names":[],"sources":["../../src/aggregation/resolveResultsRequest.ts"],"sourcesContent":["import type { Payload, PayloadRequest } from 'payload'\nimport { FORMS_SLUG } from '../collections/forms'\nimport { isPollClosed, pollConfigOf } from '../form/pollState'\nimport { type PollFormLike, shouldAutoResolvePoll } from '../poll/closeJob'\nimport type { PollOption } from '../poll/definePollOptionSource'\nimport { resolveEffectivePollOptions } from '../poll/effectivePollOptions'\nimport { resolvePollOutcome } from '../poll/resolvePollOutcome'\nimport type { FormFieldInstance } from '../submissions/types'\nimport { aggregateFormResponses, fieldHasOptions } from './aggregateResponses'\nimport type { FieldAggregation } from './types'\n\nexport type FormResultsAccessArgs = {\n\treq: PayloadRequest\n\t/** The loaded forms document (depth 0). Untyped beyond `id`: hosts read their own fields (e.g. `form.tenant`). */\n\tform: { id: number | string } & Record<string, unknown>\n}\n\n/**\n * Host seam gating anonymous results reads, evaluated after the form is loaded and before anything\n * is aggregated. Multi-tenant recipe: compare `form.tenant` against the tenant derived from `req`\n * (host header, cookie, or auth context) and return `false` for a cross-tenant read. Authenticated\n * callers bypass this seam (they are admin-trusted, like the rest of the results endpoint).\n */\nexport type FormResultsAccess = (args: FormResultsAccessArgs) => boolean | Promise<boolean>\n\nexport type ResolveResultsRequestArgs = {\n\tpayload: Payload\n\tformId: number | string | undefined\n\t/** The requested field (query param). */\n\tfield?: string\n\t/** Whether the caller is authenticated (an admin/user). */\n\tisAuthed: boolean\n\treq?: PayloadRequest\n\t/** Optional host seam for anonymous reads; absent means plugin-default gating only. */\n\taccess?: FormResultsAccess\n\t/**\n\t * Poll-eligible field types (registry-derived). When set, an anonymous read of an\n\t * option-source poll also requires the results field's instance to be one of these types,\n\t * so legacy or db-written docs pointing at a free-text field can never expose stored\n\t * answers as leftover result buckets.\n\t */\n\teligibleTypes?: readonly string[]\n}\n\nexport type ResolveResultsRequestResult = {\n\tstatus: number\n\tbody: { results: FieldAggregation[] } | { errors: { message: string }[] }\n}\n\nconst forbidden: ResolveResultsRequestResult = {\n\tstatus: 403,\n\tbody: { errors: [{ message: 'Forbidden' }] },\n}\n\n// 503 over 403 for a failed option-source resolve: the form and its poll config are already\n// publicly readable, so \"temporarily unavailable\" leaks nothing new, matches the submission\n// path's precedent, and does not mislabel a transient server fault as an authorization denial.\nconst unavailable: ResolveResultsRequestResult = {\n\tstatus: 503,\n\tbody: { errors: [{ message: 'Poll options unavailable' }] },\n}\n\n/**\n * Authorize and resolve a poll/survey results request. Authed callers may aggregate any field (or all\n * enumerable fields) and bypass the `access` seam; for a poll-enabled form they also get the results\n * field's effective options injected so sourced/resolver-backed polls render labels (best-effort, a\n * resolve failure degrades to raw values rather than blocking the trusted read). Anonymous callers are\n * allowed only when the form's poll is enabled, the poll's `resultsVisibility` permits it (`afterVote`:\n * any time; `afterClose`: only once `closesAt` has passed), and the optional host `access` seam\n * approves; and then only for the configured `poll.resultsField`, and only if that field is enumerable\n * so a misconfigured `resultsField` pointing at a free-text or PII field can never be dumped publicly. A\n * static poll is enumerable through its authored options; a poll whose options come from an\n * `optionSource` or the field type's own `resolveOptions` resolves them here (registry via\n * `config.custom`, per-request cache), gated by `eligibleTypes` and enumerable only when resolution\n * yields any, with the resolved options driving bucket order and labels. Resolution failure fails\n * closed (503) on the anonymous path. Returns only aggregate counts, never raw submissions.\n */\nexport const resolveFormResultsRequest = async (\n\targs: ResolveResultsRequestArgs\n): Promise<ResolveResultsRequestResult> => {\n\tconst { payload, formId, field, isAuthed, req, access, eligibleTypes } = args\n\tif (formId == null) {\n\t\treturn { status: 400, body: { errors: [{ message: 'Missing form id' }] } }\n\t}\n\tconst form = await payload\n\t\t.findByID({ collection: FORMS_SLUG, id: formId, depth: 0, overrideAccess: true, req })\n\t\t.catch(() => null)\n\tif (!form) {\n\t\treturn { status: 404, body: { errors: [{ message: 'Not found' }] } }\n\t}\n\n\t// No-runner safety net: when a closed poll's `mostVoted`/`source` outcome was never auto-resolved\n\t// (no wired job runner fired the close task), a results read heals it. Idempotent and gate-neutral,\n\t// it runs on the already-loaded form via overrideAccess and writes only through the outcome hook, so\n\t// it cannot relax the anonymous authorization below. A failure degrades silently to the normal path.\n\tif (shouldAutoResolvePoll(form as PollFormLike)) {\n\t\tawait resolvePollOutcome({ payload, formId, req }).catch(() => undefined)\n\t}\n\n\tlet fields: string[] | undefined\n\tlet resolvedOptions: Record<string, { value: string; label: string }[]> | undefined\n\tif (isAuthed) {\n\t\tfields = field ? [field] : undefined\n\t\t// Label a poll's results field for admins: resolve its effective options (a source, the\n\t\t// field type's own resolver, or authored options) and inject them so a sourced/resolved poll\n\t\t// shows labels instead of raw stored values, and so an all-fields read still surfaces a\n\t\t// resolver-backed field that carries no authored options. Best-effort: a resolver/source\n\t\t// failure degrades to raw values rather than blocking the trusted read (the anonymous path\n\t\t// below fails closed instead). Only run when the read would actually surface the results field.\n\t\tconst poll = pollConfigOf(form.poll)\n\t\tconst resultsField =\n\t\t\ttypeof poll?.resultsField === 'string' && poll.resultsField.length > 0\n\t\t\t\t? poll.resultsField\n\t\t\t\t: undefined\n\t\tif (form.pollEnabled === true && resultsField && (!field || field === resultsField)) {\n\t\t\tconst options: PollOption[] = await resolveEffectivePollOptions({\n\t\t\t\tpayload,\n\t\t\t\treq,\n\t\t\t\tform,\n\t\t\t}).catch(() => [])\n\t\t\tif (options.length > 0) {\n\t\t\t\tresolvedOptions = { [resultsField]: options }\n\t\t\t}\n\t\t}\n\t} else {\n\t\tconst poll = pollConfigOf(form.poll)\n\t\tif (form.pollEnabled !== true || !poll) {\n\t\t\treturn forbidden\n\t\t}\n\t\tif (poll.resultsVisibility === 'afterClose' && !isPollClosed(poll)) {\n\t\t\treturn forbidden\n\t\t}\n\t\tif (access) {\n\t\t\t// No req means the seam cannot be evaluated; fail closed rather than skip a configured gate.\n\t\t\t// The concrete generated Form doc has no index signature; the seam's Record<string, unknown>\n\t\t\t// is the ergonomic host-facing contract, so widen the doc to it here.\n\t\t\tconst allowed = req\n\t\t\t\t? await access({ req, form: form as unknown as FormResultsAccessArgs['form'] })\n\t\t\t\t: false\n\t\t\tif (!allowed) {\n\t\t\t\treturn forbidden\n\t\t\t}\n\t\t}\n\t\tconst publicField =\n\t\t\ttypeof poll.resultsField === 'string' && poll.resultsField.length > 0\n\t\t\t\t? poll.resultsField\n\t\t\t\t: undefined\n\t\tif (!publicField) {\n\t\t\treturn forbidden\n\t\t}\n\t\tif (field && field !== publicField) {\n\t\t\treturn forbidden\n\t\t}\n\t\tconst instances = Array.isArray(form.fields) ? (form.fields as FormFieldInstance[]) : []\n\t\tconst instance = instances.find((entry) => entry.name === publicField)\n\t\tif (!instance) {\n\t\t\treturn forbidden\n\t\t}\n\t\tconst hasSource = typeof poll.optionSource === 'string' && poll.optionSource.length > 0\n\t\tconst authored = fieldHasOptions(instance)\n\t\t// Options that don't come from the author's own literal `options` list (a source or a\n\t\t// field-typed resolver) require the results field to be a poll-eligible type, so a legacy or\n\t\t// db-written doc pointing `resultsField` at a free-text field can never dump raw answers as\n\t\t// leftover buckets. A static authored poll is a choice field already, so it keeps serving.\n\t\tif ((hasSource || !authored) && eligibleTypes && !eligibleTypes.includes(instance.blockType)) {\n\t\t\treturn forbidden\n\t\t}\n\t\tlet options: PollOption[]\n\t\ttry {\n\t\t\toptions = await resolveEffectivePollOptions({ payload, req, form })\n\t\t} catch {\n\t\t\treturn unavailable\n\t\t}\n\t\tif (options.length === 0) {\n\t\t\treturn forbidden\n\t\t}\n\t\tresolvedOptions = { [publicField]: options }\n\t\tfields = [publicField]\n\t}\n\n\tconst results = await aggregateFormResponses({ payload, formId, fields, req, resolvedOptions })\n\treturn { status: 200, body: { results } }\n}\n"],"mappings":";;;;;;;AAiDA,MAAM,YAAyC;CAC9C,QAAQ;CACR,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,YAAY,CAAC,EAAE;AAC5C;AAKA,MAAM,cAA2C;CAChD,QAAQ;CACR,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,2BAA2B,CAAC,EAAE;AAC3D;;;;;;;;;;;;;;;;AAiBA,MAAa,4BAA4B,OACxC,SAC0C;CAC1C,MAAM,EAAE,SAAS,QAAQ,OAAO,UAAU,KAAK,QAAQ,kBAAkB;CACzE,IAAI,UAAU,MACb,OAAO;EAAE,QAAQ;EAAK,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,kBAAkB,CAAC,EAAE;CAAE;CAE1E,MAAM,OAAO,MAAM,QACjB,SAAS;EAAE,YAAY;EAAY,IAAI;EAAQ,OAAO;EAAG,gBAAgB;EAAM;CAAI,CAAC,EACpF,YAAY,IAAI;CAClB,IAAI,CAAC,MACJ,OAAO;EAAE,QAAQ;EAAK,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,YAAY,CAAC,EAAE;CAAE;CAOpE,IAAI,sBAAsB,IAAoB,GAC7C,MAAM,mBAAmB;EAAE;EAAS;EAAQ;CAAI,CAAC,EAAE,YAAY,KAAA,CAAS;CAGzE,IAAI;CACJ,IAAI;CACJ,IAAI,UAAU;EACb,SAAS,QAAQ,CAAC,KAAK,IAAI,KAAA;EAO3B,MAAM,OAAO,aAAa,KAAK,IAAI;EACnC,MAAM,eACL,OAAO,MAAM,iBAAiB,YAAY,KAAK,aAAa,SAAS,IAClE,KAAK,eACL,KAAA;EACJ,IAAI,KAAK,gBAAgB,QAAQ,iBAAiB,CAAC,SAAS,UAAU,eAAe;GACpF,MAAM,UAAwB,MAAM,4BAA4B;IAC/D;IACA;IACA;GACD,CAAC,EAAE,YAAY,CAAC,CAAC;GACjB,IAAI,QAAQ,SAAS,GACpB,kBAAkB,GAAG,eAAe,QAAQ;EAE9C;CACD,OAAO;EACN,MAAM,OAAO,aAAa,KAAK,IAAI;EACnC,IAAI,KAAK,gBAAgB,QAAQ,CAAC,MACjC,OAAO;EAER,IAAI,KAAK,sBAAsB,gBAAgB,CAAC,aAAa,IAAI,GAChE,OAAO;EAER,IAAI;OAOC,EAHY,MACb,MAAM,OAAO;IAAE;IAAW;GAAiD,CAAC,IAC5E,QAEF,OAAO;EAAA;EAGT,MAAM,cACL,OAAO,KAAK,iBAAiB,YAAY,KAAK,aAAa,SAAS,IACjE,KAAK,eACL,KAAA;EACJ,IAAI,CAAC,aACJ,OAAO;EAER,IAAI,SAAS,UAAU,aACtB,OAAO;EAGR,MAAM,YADY,MAAM,QAAQ,KAAK,MAAM,IAAK,KAAK,SAAiC,CAAC,GAC5D,MAAM,UAAU,MAAM,SAAS,WAAW;EACrE,IAAI,CAAC,UACJ,OAAO;EAER,MAAM,YAAY,OAAO,KAAK,iBAAiB,YAAY,KAAK,aAAa,SAAS;EACtF,MAAM,WAAW,gBAAgB,QAAQ;EAKzC,KAAK,aAAa,CAAC,aAAa,iBAAiB,CAAC,cAAc,SAAS,SAAS,SAAS,GAC1F,OAAO;EAER,IAAI;EACJ,IAAI;GACH,UAAU,MAAM,4BAA4B;IAAE;IAAS;IAAK;GAAK,CAAC;EACnE,QAAQ;GACP,OAAO;EACR;EACA,IAAI,QAAQ,WAAW,GACtB,OAAO;EAER,kBAAkB,GAAG,cAAc,QAAQ;EAC3C,SAAS,CAAC,WAAW;CACtB;CAGA,OAAO;EAAE,QAAQ;EAAK,MAAM,EAAE,SAAA,MADR,uBAAuB;GAAE;GAAS;GAAQ;GAAQ;GAAK;EAAgB,CAAC,EACxD;CAAE;AACzC"}
|