@10x-media/form-builder 0.1.0-beta.4 → 0.1.0-beta.5
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 +83 -0
- package/README.md +3 -3
- package/dist/actions/body/serializeBody.d.ts +8 -5
- package/dist/actions/body/serializeBody.js.map +1 -1
- 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/builtin/confirmation.js +41 -11
- package/dist/actions/builtin/confirmation.js.map +1 -1
- package/dist/actions/builtin/emailTeam.js +77 -38
- package/dist/actions/builtin/emailTeam.js.map +1 -1
- package/dist/actions/builtin/index.d.ts +8 -3
- package/dist/actions/builtin/index.js +8 -5
- package/dist/actions/builtin/index.js.map +1 -1
- package/dist/actions/defineAction.d.ts +1 -1
- 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/task.js +9 -8
- 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 +19 -6
- package/dist/aggregation/resolveResultsRequest.js +58 -12
- package/dist/aggregation/resolveResultsRequest.js.map +1 -1
- package/dist/calc/computeCalcFields.js +2 -1
- package/dist/calc/computeCalcFields.js.map +1 -1
- package/dist/client/ByteSizeField.d.ts +8 -0
- package/dist/client/ByteSizeField.js +3 -1
- package/dist/client/ByteSizeField.js.map +1 -1
- 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/EndpointOptionsSelect.d.ts +45 -0
- package/dist/client/EndpointOptionsSelect.js +103 -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 +20 -4
- package/dist/client/FieldNameSelect.js +25 -14
- package/dist/client/FieldNameSelect.js.map +1 -1
- package/dist/client/FlowBuilder.d.ts +2 -1
- package/dist/client/FlowBuilder.js +63 -57
- package/dist/client/FlowBuilder.js.map +1 -1
- 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 +36 -0
- package/dist/client/endpointOptions.js +29 -0
- package/dist/client/endpointOptions.js.map +1 -0
- package/dist/client/flowAuthoring.js +114 -7
- package/dist/client/flowAuthoring.js.map +1 -1
- package/dist/client/synthesizeClientField.js +15 -8
- package/dist/client/synthesizeClientField.js.map +1 -1
- 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 +22 -17
- package/dist/collections/formSubmissions.js.map +1 -1
- package/dist/collections/forms.js +310 -137
- package/dist/collections/forms.js.map +1 -1
- package/dist/collections/redirectFields.d.ts +29 -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 +19 -8
- package/dist/consent/captureConsent.js +34 -11
- package/dist/consent/captureConsent.js.map +1 -1
- package/dist/consent/consentSourcesField.d.ts +58 -0
- package/dist/consent/consentSourcesField.js +79 -0
- package/dist/consent/consentSourcesField.js.map +1 -0
- package/dist/consent/resolveConsentEntries.d.ts +24 -0
- package/dist/consent/resolveConsentEntries.js +43 -0
- package/dist/consent/resolveConsentEntries.js.map +1 -0
- package/dist/consent/resolveConsentSourcesRequest.d.ts +37 -0
- package/dist/consent/resolveConsentSourcesRequest.js +55 -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 +17 -11
- package/dist/consent/resolvePublishedVersionRef.js +13 -10
- package/dist/consent/resolvePublishedVersionRef.js.map +1 -1
- package/dist/consent/types.d.ts +49 -0
- package/dist/email/departments.d.ts +55 -0
- package/dist/email/departments.js +130 -0
- package/dist/email/departments.js.map +1 -0
- package/dist/email/departmentsField.d.ts +49 -0
- package/dist/email/departmentsField.js +69 -0
- package/dist/email/departmentsField.js.map +1 -0
- package/dist/exports/client.d.ts +8 -1
- package/dist/exports/client.js +8 -1
- package/dist/exports/react.d.ts +8 -3
- package/dist/exports/react.js +6 -2
- package/dist/exports/rsc.d.ts +3 -1
- package/dist/exports/rsc.js +4 -2
- package/dist/exports/types.d.ts +16 -8
- package/dist/fields/buildFieldBlocks.js +28 -13
- package/dist/fields/buildFieldBlocks.js.map +1 -1
- package/dist/fields/builtin/calculation.js +7 -0
- package/dist/fields/builtin/calculation.js.map +1 -1
- package/dist/fields/builtin/checkbox.js +8 -0
- package/dist/fields/builtin/checkbox.js.map +1 -1
- package/dist/fields/builtin/consent.js +27 -21
- 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 +2 -0
- package/dist/fields/builtin/date.js.map +1 -1
- package/dist/fields/builtin/file.js +5 -4
- package/dist/fields/builtin/file.js.map +1 -1
- package/dist/fields/builtin/index.d.ts +8 -6
- package/dist/fields/builtin/index.js +10 -9
- package/dist/fields/builtin/index.js.map +1 -1
- package/dist/fields/builtin/message.js +8 -4
- package/dist/fields/builtin/message.js.map +1 -1
- package/dist/fields/builtin/repeater.js +1 -0
- package/dist/fields/builtin/repeater.js.map +1 -1
- package/dist/fields/builtin/select.js +1 -0
- 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/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 +13 -8
- package/dist/fields/fieldNamesOfType.js.map +1 -1
- package/dist/fields/instanceOptions.js +20 -0
- package/dist/fields/instanceOptions.js.map +1 -0
- package/dist/fields/registry.d.ts +1 -0
- package/dist/fields/registry.js +21 -2
- package/dist/fields/registry.js.map +1 -1
- package/dist/fields/sharedConfig.js +46 -9
- package/dist/fields/sharedConfig.js.map +1 -1
- package/dist/fields/types.d.ts +54 -3
- 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 +16 -7
- package/dist/flow/normalizeFlow.js.map +1 -1
- 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 +4 -4
- package/dist/form/pollState.js.map +1 -1
- package/dist/form/toFormDocument.d.ts +37 -12
- package/dist/form/toFormDocument.js +43 -7
- package/dist/form/toFormDocument.js.map +1 -1
- package/dist/form/types.d.ts +31 -14
- package/dist/index.d.ts +143 -28
- package/dist/index.js +43 -14
- package/dist/index.js.map +1 -1
- package/dist/plugin/customState.js +26 -0
- package/dist/plugin/customState.js.map +1 -0
- package/dist/plugin/registerCollections.js +23 -13
- package/dist/plugin/registerCollections.js.map +1 -1
- package/dist/plugin/uploadsCollection.d.ts +2 -0
- package/dist/poll/applyPollOptions.js +3 -2
- package/dist/poll/applyPollOptions.js.map +1 -1
- package/dist/poll/buildPollOptionSourceFields.js +2 -5
- package/dist/poll/buildPollOptionSourceFields.js.map +1 -1
- 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 +10 -1
- package/dist/poll/definePollOptionSource.js.map +1 -1
- 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 +71 -0
- package/dist/poll/pollTypeRegistry.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 +2 -1
- package/dist/poll/resolvePollOptions.js +5 -17
- package/dist/poll/resolvePollOptions.js.map +1 -1
- package/dist/poll/resolvePollOptionsRequest.js +51 -0
- package/dist/poll/resolvePollOptionsRequest.js.map +1 -0
- package/dist/poll/resolvePollOutcome.d.ts +22 -9
- package/dist/poll/resolvePollOutcome.js +54 -14
- package/dist/poll/resolvePollOutcome.js.map +1 -1
- package/dist/poll/resultsField.js +21 -0
- package/dist/poll/resultsField.js.map +1 -0
- package/dist/prefill/valuesFromSearchParams.js +2 -1
- package/dist/prefill/valuesFromSearchParams.js.map +1 -1
- package/dist/react/Form.d.ts +8 -8
- package/dist/react/Form.js +74 -62
- package/dist/react/Form.js.map +1 -1
- package/dist/react/FormContext.d.ts +6 -4
- package/dist/react/FormContext.js.map +1 -1
- package/dist/react/FormControls.d.ts +3 -3
- package/dist/react/FormControls.js +3 -3
- package/dist/react/FormControls.js.map +1 -1
- package/dist/react/FormResults.d.ts +5 -5
- package/dist/react/FormResults.js +6 -6
- package/dist/react/FormResults.js.map +1 -1
- package/dist/react/Poll.d.ts +2 -2
- package/dist/react/Poll.js +5 -5
- package/dist/react/Poll.js.map +1 -1
- package/dist/react/contract.d.ts +2 -3
- package/dist/react/contract.js.map +1 -1
- package/dist/react/primitives/Checkbox.d.ts +4 -2
- package/dist/react/primitives/Checkbox.js +2 -1
- package/dist/react/primitives/Checkbox.js.map +1 -1
- package/dist/react/primitives/FieldShell.d.ts +2 -4
- package/dist/react/primitives/FieldShell.js +12 -19
- package/dist/react/primitives/FieldShell.js.map +1 -1
- package/dist/react/renderers/calculation.js +1 -2
- package/dist/react/renderers/calculation.js.map +1 -1
- package/dist/react/renderers/checkbox.js +1 -2
- package/dist/react/renderers/checkbox.js.map +1 -1
- package/dist/react/renderers/consent.js +78 -34
- package/dist/react/renderers/consent.js.map +1 -1
- package/dist/react/renderers/date.js +1 -3
- package/dist/react/renderers/date.js.map +1 -1
- package/dist/react/renderers/email.js +1 -2
- package/dist/react/renderers/email.js.map +1 -1
- package/dist/react/renderers/file.js +1 -2
- package/dist/react/renderers/file.js.map +1 -1
- package/dist/react/renderers/index.js +3 -0
- package/dist/react/renderers/index.js.map +1 -1
- package/dist/react/renderers/number.js +1 -2
- package/dist/react/renderers/number.js.map +1 -1
- package/dist/react/renderers/region.js +45 -0
- package/dist/react/renderers/region.js.map +1 -0
- package/dist/react/renderers/repeater.js +7 -10
- package/dist/react/renderers/repeater.js.map +1 -1
- package/dist/react/renderers/select.js +1 -2
- package/dist/react/renderers/select.js.map +1 -1
- package/dist/react/renderers/text.js +1 -2
- package/dist/react/renderers/text.js.map +1 -1
- package/dist/react/renderers/textarea.js +1 -2
- package/dist/react/renderers/textarea.js.map +1 -1
- package/dist/react/state.d.ts +0 -3
- package/dist/react/state.js +7 -11
- package/dist/react/state.js.map +1 -1
- package/dist/react/useField.d.ts +0 -1
- package/dist/react/useField.js +0 -1
- 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 +14 -2
- package/dist/recall/resolver.js.map +1 -1
- package/dist/spam/captcha.d.ts +1 -1
- package/dist/spam/captcha.js +1 -1
- package/dist/spam/captcha.js.map +1 -1
- package/dist/submissions/SubmissionAnswers.js +6 -2
- package/dist/submissions/SubmissionAnswers.js.map +1 -1
- package/dist/submissions/SubmissionAnswersClient.js +91 -62
- package/dist/submissions/SubmissionAnswersClient.js.map +1 -1
- 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 +29 -18
- package/dist/submissions/runSubmission.js.map +1 -1
- package/dist/submissions/types.d.ts +11 -4
- package/dist/submissions/validateSubmission.js +38 -23
- package/dist/submissions/validateSubmission.js.map +1 -1
- package/dist/submissions/votedCookie.js +3 -3
- package/dist/submissions/votedCookie.js.map +1 -1
- package/dist/translations/de.js +102 -26
- package/dist/translations/de.js.map +1 -1
- package/dist/translations/en.js +103 -27
- package/dist/translations/en.js.map +1 -1
- package/dist/translations/keys.d.ts +97 -21
- package/dist/translations/keys.js +98 -22
- package/dist/translations/keys.js.map +1 -1
- package/dist/validation/buildRuleBlocks.js +18 -17
- package/dist/validation/buildRuleBlocks.js.map +1 -1
- package/dist/validation/builtin/email.js +1 -0
- package/dist/validation/builtin/email.js.map +1 -1
- 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 +1 -0
- package/dist/validation/builtin/maxDate.js.map +1 -1
- 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 +1 -0
- package/dist/validation/builtin/minDate.js.map +1 -1
- package/dist/validation/builtin/minLength.js +1 -0
- package/dist/validation/builtin/minLength.js.map +1 -1
- package/dist/validation/builtin/notAlreadySubmitted.js +1 -0
- 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/validateRegexParams.js +44 -0
- package/dist/validation/builtin/validateRegexParams.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/runValidation.js +8 -29
- package/dist/validation/runValidation.js.map +1 -1
- package/dist/validation/types.d.ts +5 -11
- package/package.json +6 -6
- package/dist/consent/buildConsentSourceConfig.js +0 -49
- package/dist/consent/buildConsentSourceConfig.js.map +0 -1
- package/dist/consent/builtin/index.d.ts +0 -17
- package/dist/consent/builtin/index.js +0 -21
- 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 -48
- 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
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import { registerPollCloseTask } from "../poll/closeJob.js";
|
|
1
2
|
import { buildFormsCollection } from "../collections/forms.js";
|
|
2
3
|
import { buildSubmissionsCollection } from "../collections/formSubmissions.js";
|
|
3
4
|
import { registerActionsTask } from "../actions/task.js";
|
|
4
5
|
import { attachUploadsCollection } from "./uploadsCollection.js";
|
|
5
6
|
//#region src/plugin/registerCollections.ts
|
|
6
|
-
const registerCollections = ({ config, registry, ruleRegistry,
|
|
7
|
+
const registerCollections = ({ config, registry, ruleRegistry, consentSources, actionRegistry, richText, hasJobsPlugin, events, uploads, spam, showSubmissionRawFields, localizeContent, resultsAccess, votedCookie, pollSourceRegistry, pollTypeRegistry, outcomeFields, buttons, response, fromAddresses, departments, redirectRelationships, overrides }) => {
|
|
7
8
|
registerActionsTask(config, actionRegistry, richText);
|
|
9
|
+
registerPollCloseTask(config);
|
|
8
10
|
const hasRunner = Boolean(config.jobs?.autoRun) || hasJobsPlugin;
|
|
9
11
|
const uploadSlug = uploads === false ? void 0 : uploads.collection;
|
|
10
12
|
if (uploads !== false) attachUploadsCollection({
|
|
@@ -14,21 +16,10 @@ const registerCollections = ({ config, registry, ruleRegistry, consentRegistry,
|
|
|
14
16
|
});
|
|
15
17
|
config.collections = [
|
|
16
18
|
...config.collections ?? [],
|
|
17
|
-
buildFormsCollection({
|
|
18
|
-
registry,
|
|
19
|
-
ruleRegistry,
|
|
20
|
-
consentRegistry,
|
|
21
|
-
actionRegistry,
|
|
22
|
-
localizeContent,
|
|
23
|
-
uploadsCollectionSlug: uploadSlug,
|
|
24
|
-
resultsAccess,
|
|
25
|
-
pollSourceRegistry,
|
|
26
|
-
overrides: overrides?.forms
|
|
27
|
-
}),
|
|
28
19
|
buildSubmissionsCollection({
|
|
29
20
|
registry,
|
|
30
21
|
ruleRegistry,
|
|
31
|
-
|
|
22
|
+
consentSources,
|
|
32
23
|
actionRegistry,
|
|
33
24
|
richText,
|
|
34
25
|
events,
|
|
@@ -39,6 +30,25 @@ const registerCollections = ({ config, registry, ruleRegistry, consentRegistry,
|
|
|
39
30
|
pollSourceRegistry,
|
|
40
31
|
showRawFields: showSubmissionRawFields,
|
|
41
32
|
overrides: overrides?.formSubmissions
|
|
33
|
+
}),
|
|
34
|
+
buildFormsCollection({
|
|
35
|
+
registry,
|
|
36
|
+
ruleRegistry,
|
|
37
|
+
consentSources,
|
|
38
|
+
actionRegistry,
|
|
39
|
+
localizeContent,
|
|
40
|
+
richText,
|
|
41
|
+
uploadsCollectionSlug: uploadSlug,
|
|
42
|
+
resultsAccess,
|
|
43
|
+
pollSourceRegistry,
|
|
44
|
+
pollTypeRegistry,
|
|
45
|
+
outcomeFields,
|
|
46
|
+
buttons,
|
|
47
|
+
response,
|
|
48
|
+
fromAddresses,
|
|
49
|
+
departments,
|
|
50
|
+
redirectRelationships,
|
|
51
|
+
overrides: overrides?.forms
|
|
42
52
|
})
|
|
43
53
|
];
|
|
44
54
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registerCollections.js","names":[],"sources":["../../src/plugin/registerCollections.ts"],"sourcesContent":["import type { Config } from 'payload'\nimport type { RichTextBodyOption } from '../actions/body/serializeBody'\nimport type { ActionRegistry } from '../actions/registry'\nimport { registerActionsTask } from '../actions/task'\nimport type { FormResultsAccess } from '../aggregation/resolveResultsRequest'\nimport { buildSubmissionsCollection } from '../collections/formSubmissions'\nimport { buildFormsCollection } from '../collections/forms'\nimport type {
|
|
1
|
+
{"version":3,"file":"registerCollections.js","names":[],"sources":["../../src/plugin/registerCollections.ts"],"sourcesContent":["import type { CollectionSlug, Config } from 'payload'\nimport type { RichTextBodyOption } from '../actions/body/serializeBody'\nimport type { FromAddressesResolver } from '../actions/fromAddresses'\nimport type { ActionRegistry } from '../actions/registry'\nimport { registerActionsTask } from '../actions/task'\nimport type { FormResultsAccess } from '../aggregation/resolveResultsRequest'\nimport type { ButtonsOption } from '../collections/buttonFields'\nimport { buildSubmissionsCollection } from '../collections/formSubmissions'\nimport { buildFormsCollection } from '../collections/forms'\nimport type { ResponseOption } from '../collections/redirectFields'\nimport type { ConsentSourcesResolver } from '../consent/types'\nimport type { DepartmentEmailsResolver } from '../email/departments'\nimport type { FormEventSink } from '../events/types'\nimport type { FieldTypeRegistry } from '../fields/registry'\nimport { registerPollCloseTask } from '../poll/closeJob'\nimport type { OutcomeFieldsOverride } from '../poll/outcomeFields'\nimport type { PollTypeRegistry } from '../poll/pollTypeRegistry'\nimport type { PollOptionSourceRegistry } from '../poll/registry'\nimport type { ResolvedSpamConfig } from '../spam/types'\nimport type { ValidationRuleRegistry } from '../validation/registry'\nimport type { CollectionOverrides } from './collectionOverrides'\nimport { attachUploadsCollection, type UploadsOption } from './uploadsCollection'\n\ntype RegisterCollectionsArgs = {\n\tconfig: Config\n\tregistry: FieldTypeRegistry\n\truleRegistry: ValidationRuleRegistry\n\tconsentSources?: ConsentSourcesResolver\n\tactionRegistry: ActionRegistry\n\trichText?: RichTextBodyOption\n\thasJobsPlugin: boolean\n\tevents?: FormEventSink\n\tuploads: UploadsOption\n\tspam: ResolvedSpamConfig | false\n\tshowSubmissionRawFields: boolean\n\tlocalizeContent: boolean\n\tresultsAccess?: FormResultsAccess\n\tvotedCookie: boolean\n\tpollSourceRegistry: PollOptionSourceRegistry\n\tpollTypeRegistry: PollTypeRegistry\n\toutcomeFields?: OutcomeFieldsOverride\n\tbuttons?: ButtonsOption\n\tresponse?: ResponseOption\n\tfromAddresses?: FromAddressesResolver\n\tdepartments?: DepartmentEmailsResolver\n\tredirectRelationships?: CollectionSlug[]\n\toverrides?: {\n\t\tforms?: CollectionOverrides\n\t\tformSubmissions?: CollectionOverrides\n\t}\n}\n\nexport const registerCollections = ({\n\tconfig,\n\tregistry,\n\truleRegistry,\n\tconsentSources,\n\tactionRegistry,\n\trichText,\n\thasJobsPlugin,\n\tevents,\n\tuploads,\n\tspam,\n\tshowSubmissionRawFields,\n\tlocalizeContent,\n\tresultsAccess,\n\tvotedCookie,\n\tpollSourceRegistry,\n\tpollTypeRegistry,\n\toutcomeFields,\n\tbuttons,\n\tresponse,\n\tfromAddresses,\n\tdepartments,\n\tredirectRelationships,\n\toverrides,\n}: RegisterCollectionsArgs): void => {\n\tregisterActionsTask(config, actionRegistry, richText)\n\tregisterPollCloseTask(config)\n\tconst hasRunner = Boolean(config.jobs?.autoRun) || hasJobsPlugin\n\n\tconst uploadSlug = uploads === false ? undefined : uploads.collection\n\tif (uploads !== false) {\n\t\tattachUploadsCollection({ config, slug: uploads.collection, spam })\n\t}\n\n\t// Payload has no nav-sort option, so it lists collections in registration order. Register\n\t// `form-submissions` before `forms` so the \"Forms\" nav group reads alphabetically\n\t// (`form-submissions` < `forms`); ordering plugin nav groups against a host's own collections\n\t// stays a host concern.\n\tconfig.collections = [\n\t\t...(config.collections ?? []),\n\t\tbuildSubmissionsCollection({\n\t\t\tregistry,\n\t\t\truleRegistry,\n\t\t\tconsentSources,\n\t\t\tactionRegistry,\n\t\t\trichText,\n\t\t\tevents,\n\t\t\thasRunner,\n\t\t\tuploadSlug,\n\t\t\tspam,\n\t\t\tvotedCookie,\n\t\t\tpollSourceRegistry,\n\t\t\tshowRawFields: showSubmissionRawFields,\n\t\t\toverrides: overrides?.formSubmissions,\n\t\t}),\n\t\tbuildFormsCollection({\n\t\t\tregistry,\n\t\t\truleRegistry,\n\t\t\tconsentSources,\n\t\t\tactionRegistry,\n\t\t\tlocalizeContent,\n\t\t\trichText,\n\t\t\tuploadsCollectionSlug: uploadSlug,\n\t\t\tresultsAccess,\n\t\t\tpollSourceRegistry,\n\t\t\tpollTypeRegistry,\n\t\t\toutcomeFields,\n\t\t\tbuttons,\n\t\t\tresponse,\n\t\t\tfromAddresses,\n\t\t\tdepartments,\n\t\t\tredirectRelationships,\n\t\t\toverrides: overrides?.forms,\n\t\t}),\n\t]\n}\n"],"mappings":";;;;;;AAoDA,MAAa,uBAAuB,EACnC,QACA,UACA,cACA,gBACA,gBACA,UACA,eACA,QACA,SACA,MACA,yBACA,iBACA,eACA,aACA,oBACA,kBACA,eACA,SACA,UACA,eACA,aACA,uBACA,gBACoC;CACpC,oBAAoB,QAAQ,gBAAgB,QAAQ;CACpD,sBAAsB,MAAM;CAC5B,MAAM,YAAY,QAAQ,OAAO,MAAM,OAAO,KAAK;CAEnD,MAAM,aAAa,YAAY,QAAQ,KAAA,IAAY,QAAQ;CAC3D,IAAI,YAAY,OACf,wBAAwB;EAAE;EAAQ,MAAM,QAAQ;EAAY;CAAK,CAAC;CAOnE,OAAO,cAAc;EACpB,GAAI,OAAO,eAAe,CAAC;EAC3B,2BAA2B;GAC1B;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA,eAAe;GACf,WAAW,WAAW;EACvB,CAAC;EACD,qBAAqB;GACpB;GACA;GACA;GACA;GACA;GACA;GACA,uBAAuB;GACvB;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA,WAAW,WAAW;EACvB,CAAC;CACF;AACD"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
//#region src/poll/applyPollOptions.ts
|
|
2
2
|
/**
|
|
3
3
|
* Replace the options of the field instance named `resultsField` with source-resolved options,
|
|
4
|
-
* without mutating the input. No-op when `resultsField` is unset or matches no instance
|
|
5
|
-
* hand-authored options stay untouched on forms without
|
|
4
|
+
* without mutating the input. No-op when `resultsField` is unset or matches no instance
|
|
5
|
+
* (nameless bare rows never match), so hand-authored options stay untouched on forms without
|
|
6
|
+
* an option source.
|
|
6
7
|
*/
|
|
7
8
|
const applyPollOptions = (fields, resultsField, options) => {
|
|
8
9
|
if (typeof resultsField !== "string" || resultsField.length === 0) return fields;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"applyPollOptions.js","names":[],"sources":["../../src/poll/applyPollOptions.ts"],"sourcesContent":["import type { PollOption } from './definePollOptionSource'\n\n/**\n * Replace the options of the field instance named `resultsField` with source-resolved options,\n * without mutating the input. No-op when `resultsField` is unset or matches no instance
|
|
1
|
+
{"version":3,"file":"applyPollOptions.js","names":[],"sources":["../../src/poll/applyPollOptions.ts"],"sourcesContent":["import type { PollOption } from './definePollOptionSource'\n\n/**\n * Replace the options of the field instance named `resultsField` with source-resolved options,\n * without mutating the input. No-op when `resultsField` is unset or matches no instance\n * (nameless bare rows never match), so hand-authored options stay untouched on forms without\n * an option source.\n */\nexport const applyPollOptions = <T extends { name?: string }>(\n\tfields: T[],\n\tresultsField: string | null | undefined,\n\toptions: PollOption[]\n): T[] => {\n\tif (typeof resultsField !== 'string' || resultsField.length === 0) {\n\t\treturn fields\n\t}\n\treturn fields.map((instance) =>\n\t\tinstance.name === resultsField ? { ...instance, options } : instance\n\t)\n}\n"],"mappings":";;;;;;;AAQA,MAAa,oBACZ,QACA,cACA,YACS;CACT,IAAI,OAAO,iBAAiB,YAAY,aAAa,WAAW,GAC/D,OAAO;CAER,OAAO,OAAO,KAAK,aAClB,SAAS,SAAS,eAAe;EAAE,GAAG;EAAU;CAAQ,IAAI,QAC7D;AACD"}
|
|
@@ -27,10 +27,7 @@ const buildPollOptionSourceFields = (registry) => {
|
|
|
27
27
|
name: "optionSource",
|
|
28
28
|
type: "select",
|
|
29
29
|
label: labelForKey(keys.pollOptionSource),
|
|
30
|
-
admin: {
|
|
31
|
-
description: labelForKey(keys.pollOptionSourceDescription),
|
|
32
|
-
condition: (_data, siblingData) => Boolean(siblingData?.enabled)
|
|
33
|
-
},
|
|
30
|
+
admin: { description: labelForKey(keys.pollOptionSourceDescription) },
|
|
34
31
|
options: sources.map((source) => ({
|
|
35
32
|
label: labelFor(source.label),
|
|
36
33
|
value: source.type
|
|
@@ -39,7 +36,7 @@ const buildPollOptionSourceFields = (registry) => {
|
|
|
39
36
|
name: "sourceConfig",
|
|
40
37
|
type: "group",
|
|
41
38
|
label: labelForKey(keys.pollSourceConfig),
|
|
42
|
-
admin: { condition: (_data, siblingData) => Boolean(siblingData?.
|
|
39
|
+
admin: { condition: (_data, siblingData) => Boolean(siblingData?.optionSource) },
|
|
43
40
|
fields: sourceConfigFields.length > 0 ? sourceConfigFields : [{
|
|
44
41
|
name: "_placeholder",
|
|
45
42
|
type: "text",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buildPollOptionSourceFields.js","names":[],"sources":["../../src/poll/buildPollOptionSourceFields.ts"],"sourcesContent":["import type { Field } from 'payload'\nimport { keys } from '../translations/keys'\nimport { labelFor, labelForKey } from '../translations/server'\nimport type { PollOptionSourceRegistry } from './registry'\n\n/**\n * Builds the `optionSource` select and `sourceConfig` group for the forms poll group; returns\n * nothing when no sources are registered, so the fields only exist for hosts that opted in.\n * Fields inside `sourceConfig` are annotated with `admin.condition` so only the fields of the\n * currently selected source type are visible; like consent's `sourceConfig`, field names within\n * the group must be unique across all registered sources. Unlike consent's `source` select there\n * is no default: hand-authored options remain the baseline and a source is an explicit opt-in per\n * form (the select stays clearable).\n */\nexport const buildPollOptionSourceFields = (registry: PollOptionSourceRegistry): Field[] => {\n\tconst sources = [...registry.values()]\n\tif (sources.length === 0) {\n\t\treturn []\n\t}\n\n\tconst sourceConfigFields: Field[] = sources.flatMap((source) =>\n\t\t(source.config ?? []).map((field) => {\n\t\t\tconst existingAdmin =\n\t\t\t\ttypeof field.admin === 'object' && field.admin !== null ? field.admin : {}\n\t\t\treturn {\n\t\t\t\t...field,\n\t\t\t\tadmin: {\n\t\t\t\t\t...existingAdmin,\n\t\t\t\t\tcondition: (data: unknown) =>\n\t\t\t\t\t\t(data as { poll?: { optionSource?: string } })?.poll?.optionSource === source.type,\n\t\t\t\t},\n\t\t\t} as Field\n\t\t})\n\t)\n\n\treturn [\n\t\t{\n\t\t\tname: 'optionSource',\n\t\t\ttype: 'select',\n\t\t\tlabel: labelForKey(keys.pollOptionSource),\n\t\t\tadmin: {\n\t\t\t\tdescription: labelForKey(keys.pollOptionSourceDescription),\n\t\t\t
|
|
1
|
+
{"version":3,"file":"buildPollOptionSourceFields.js","names":[],"sources":["../../src/poll/buildPollOptionSourceFields.ts"],"sourcesContent":["import type { Field } from 'payload'\nimport { keys } from '../translations/keys'\nimport { labelFor, labelForKey } from '../translations/server'\nimport type { PollOptionSourceRegistry } from './registry'\n\n/**\n * Builds the `optionSource` select and `sourceConfig` group for the forms poll group; returns\n * nothing when no sources are registered, so the fields only exist for hosts that opted in.\n * Fields inside `sourceConfig` are annotated with `admin.condition` so only the fields of the\n * currently selected source type are visible; like consent's `sourceConfig`, field names within\n * the group must be unique across all registered sources. Unlike consent's `source` select there\n * is no default: hand-authored options remain the baseline and a source is an explicit opt-in per\n * form (the select stays clearable).\n */\nexport const buildPollOptionSourceFields = (registry: PollOptionSourceRegistry): Field[] => {\n\tconst sources = [...registry.values()]\n\tif (sources.length === 0) {\n\t\treturn []\n\t}\n\n\tconst sourceConfigFields: Field[] = sources.flatMap((source) =>\n\t\t(source.config ?? []).map((field) => {\n\t\t\tconst existingAdmin =\n\t\t\t\ttypeof field.admin === 'object' && field.admin !== null ? field.admin : {}\n\t\t\treturn {\n\t\t\t\t...field,\n\t\t\t\tadmin: {\n\t\t\t\t\t...existingAdmin,\n\t\t\t\t\tcondition: (data: unknown) =>\n\t\t\t\t\t\t(data as { poll?: { optionSource?: string } })?.poll?.optionSource === source.type,\n\t\t\t\t},\n\t\t\t} as Field\n\t\t})\n\t)\n\n\treturn [\n\t\t{\n\t\t\tname: 'optionSource',\n\t\t\ttype: 'select',\n\t\t\tlabel: labelForKey(keys.pollOptionSource),\n\t\t\tadmin: {\n\t\t\t\tdescription: labelForKey(keys.pollOptionSourceDescription),\n\t\t\t},\n\t\t\toptions: sources.map((source) => ({ label: labelFor(source.label), value: source.type })),\n\t\t},\n\t\t{\n\t\t\tname: 'sourceConfig',\n\t\t\ttype: 'group',\n\t\t\tlabel: labelForKey(keys.pollSourceConfig),\n\t\t\tadmin: {\n\t\t\t\tcondition: (_data, siblingData) => Boolean(siblingData?.optionSource),\n\t\t\t},\n\t\t\tfields:\n\t\t\t\tsourceConfigFields.length > 0\n\t\t\t\t\t? sourceConfigFields\n\t\t\t\t\t: [{ name: '_placeholder', type: 'text', admin: { hidden: true } }],\n\t\t},\n\t]\n}\n"],"mappings":";;;;;;;;;;;;AAcA,MAAa,+BAA+B,aAAgD;CAC3F,MAAM,UAAU,CAAC,GAAG,SAAS,OAAO,CAAC;CACrC,IAAI,QAAQ,WAAW,GACtB,OAAO,CAAC;CAGT,MAAM,qBAA8B,QAAQ,SAAS,YACnD,OAAO,UAAU,CAAC,GAAG,KAAK,UAAU;EACpC,MAAM,gBACL,OAAO,MAAM,UAAU,YAAY,MAAM,UAAU,OAAO,MAAM,QAAQ,CAAC;EAC1E,OAAO;GACN,GAAG;GACH,OAAO;IACN,GAAG;IACH,YAAY,SACV,MAA+C,MAAM,iBAAiB,OAAO;GAChF;EACD;CACD,CAAC,CACF;CAEA,OAAO,CACN;EACC,MAAM;EACN,MAAM;EACN,OAAO,YAAY,KAAK,gBAAgB;EACxC,OAAO,EACN,aAAa,YAAY,KAAK,2BAA2B,EAC1D;EACA,SAAS,QAAQ,KAAK,YAAY;GAAE,OAAO,SAAS,OAAO,KAAK;GAAG,OAAO,OAAO;EAAK,EAAE;CACzF,GACA;EACC,MAAM;EACN,MAAM;EACN,OAAO,YAAY,KAAK,gBAAgB;EACxC,OAAO,EACN,YAAY,OAAO,gBAAgB,QAAQ,aAAa,YAAY,EACrE;EACA,QACC,mBAAmB,SAAS,IACzB,qBACA,CAAC;GAAE,MAAM;GAAgB,MAAM;GAAQ,OAAO,EAAE,QAAQ,KAAK;EAAE,CAAC;CACrE,CACD;AACD"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Config, Payload, PayloadRequest, TaskConfig } from "payload";
|
|
2
|
+
|
|
3
|
+
//#region src/poll/closeJob.d.ts
|
|
4
|
+
declare const POLL_CLOSE_TASK_SLUG = "form-builder-poll-close";
|
|
5
|
+
/** Input the enqueue path stores; the handler re-loads everything else from the DB. */
|
|
6
|
+
type PollCloseTaskInput = {
|
|
7
|
+
formId: number | string;
|
|
8
|
+
};
|
|
9
|
+
/** The slice of a loaded form the close gate reads. */
|
|
10
|
+
type PollFormLike = {
|
|
11
|
+
pollEnabled?: boolean | null;
|
|
12
|
+
poll?: unknown;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* True when a poll's outcome should auto-resolve now: enabled, closed, still unresolved, and its
|
|
16
|
+
* strategy is not `manual`. Shared by the close-job handler and the results-read fallback so the two
|
|
17
|
+
* triggers apply the exact same gate.
|
|
18
|
+
*/
|
|
19
|
+
declare const shouldAutoResolvePoll: (form: PollFormLike) => boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Run the scheduled close for one form. Idempotent: re-loads the form and only resolves when the
|
|
22
|
+
* poll is enabled, closed, still unresolved, and not `manual`, so a re-run after the outcome was
|
|
23
|
+
* recorded (or a job that fires slightly early) is a safe no-op. Tolerates a deleted form.
|
|
24
|
+
*/
|
|
25
|
+
declare const runPollClose: (args: {
|
|
26
|
+
payload: Payload;
|
|
27
|
+
input: PollCloseTaskInput;
|
|
28
|
+
req?: PayloadRequest;
|
|
29
|
+
}) => Promise<void>;
|
|
30
|
+
/** Native Payload jobs task that resolves a poll's outcome once it closes. */
|
|
31
|
+
declare const buildPollCloseTask: () => TaskConfig;
|
|
32
|
+
/** Register the poll-close task on `config.jobs.tasks`, creating the jobs config if absent. */
|
|
33
|
+
declare const registerPollCloseTask: (config: Config) => void;
|
|
34
|
+
/**
|
|
35
|
+
* Schedule (or reschedule) the auto-close for a poll after a save. Enqueues a `waitUntil: closesAt`
|
|
36
|
+
* job when the poll is enabled, has a `closesAt`, is still unresolved, and its strategy is not
|
|
37
|
+
* `manual`, and only when a job runner is present (`payload.jobs.queue`); otherwise the resolve-on-read
|
|
38
|
+
* fallback is the safety net. Any prior pending close job for the form is deleted first so the newest
|
|
39
|
+
* `closesAt` wins. Never throws; a queue failure is logged and swallowed.
|
|
40
|
+
*/
|
|
41
|
+
declare const enqueuePollClose: (args: {
|
|
42
|
+
payload: Payload;
|
|
43
|
+
form: {
|
|
44
|
+
id: number | string;
|
|
45
|
+
pollEnabled?: boolean | null;
|
|
46
|
+
poll?: unknown;
|
|
47
|
+
};
|
|
48
|
+
req?: PayloadRequest;
|
|
49
|
+
}) => Promise<void>;
|
|
50
|
+
//#endregion
|
|
51
|
+
export { POLL_CLOSE_TASK_SLUG, PollCloseTaskInput, buildPollCloseTask, enqueuePollClose, registerPollCloseTask, runPollClose, shouldAutoResolvePoll };
|
|
52
|
+
//# sourceMappingURL=closeJob.d.ts.map
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { isPollClosed, pollConfigOf } from "../form/pollState.js";
|
|
2
|
+
import { resolvePollOutcome } from "./resolvePollOutcome.js";
|
|
3
|
+
import { FORMS_SLUG } from "../collections/forms.js";
|
|
4
|
+
//#region src/poll/closeJob.ts
|
|
5
|
+
const POLL_CLOSE_TASK_SLUG = "form-builder-poll-close";
|
|
6
|
+
/** Payload's jobs collection; queried directly to supersede a superseded pending close job. */
|
|
7
|
+
const JOBS_SLUG = "payload-jobs";
|
|
8
|
+
const hasEmptyWinners = (poll) => {
|
|
9
|
+
const winners = poll.outcome?.winningValues;
|
|
10
|
+
return !Array.isArray(winners) || winners.filter((value) => typeof value === "string" && value.length > 0).length === 0;
|
|
11
|
+
};
|
|
12
|
+
const strategyOf = (poll) => typeof poll.type === "string" && poll.type.length > 0 ? poll.type : "manual";
|
|
13
|
+
/**
|
|
14
|
+
* True when a poll's outcome should auto-resolve now: enabled, closed, still unresolved, and its
|
|
15
|
+
* strategy is not `manual`. Shared by the close-job handler and the results-read fallback so the two
|
|
16
|
+
* triggers apply the exact same gate.
|
|
17
|
+
*/
|
|
18
|
+
const shouldAutoResolvePoll = (form) => {
|
|
19
|
+
const poll = pollConfigOf(form.poll);
|
|
20
|
+
return form.pollEnabled === true && poll != null && isPollClosed(poll) && hasEmptyWinners(poll) && strategyOf(poll) !== "manual";
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Run the scheduled close for one form. Idempotent: re-loads the form and only resolves when the
|
|
24
|
+
* poll is enabled, closed, still unresolved, and not `manual`, so a re-run after the outcome was
|
|
25
|
+
* recorded (or a job that fires slightly early) is a safe no-op. Tolerates a deleted form.
|
|
26
|
+
*/
|
|
27
|
+
const runPollClose = async (args) => {
|
|
28
|
+
const { payload, input, req } = args;
|
|
29
|
+
const form = await payload.findByID({
|
|
30
|
+
collection: FORMS_SLUG,
|
|
31
|
+
id: input.formId,
|
|
32
|
+
depth: 0,
|
|
33
|
+
overrideAccess: true,
|
|
34
|
+
req
|
|
35
|
+
}).catch(() => null);
|
|
36
|
+
if (!form || !shouldAutoResolvePoll(form)) return;
|
|
37
|
+
await resolvePollOutcome({
|
|
38
|
+
payload,
|
|
39
|
+
formId: input.formId,
|
|
40
|
+
req
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
/** Native Payload jobs task that resolves a poll's outcome once it closes. */
|
|
44
|
+
const buildPollCloseTask = () => ({
|
|
45
|
+
slug: POLL_CLOSE_TASK_SLUG,
|
|
46
|
+
inputSchema: [{
|
|
47
|
+
name: "formId",
|
|
48
|
+
type: "text",
|
|
49
|
+
required: true
|
|
50
|
+
}],
|
|
51
|
+
handler: async ({ input, req }) => {
|
|
52
|
+
await runPollClose({
|
|
53
|
+
payload: req.payload,
|
|
54
|
+
input,
|
|
55
|
+
req
|
|
56
|
+
});
|
|
57
|
+
return { output: {} };
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
/** Register the poll-close task on `config.jobs.tasks`, creating the jobs config if absent. */
|
|
61
|
+
const registerPollCloseTask = (config) => {
|
|
62
|
+
config.jobs ??= {};
|
|
63
|
+
config.jobs.tasks ??= [];
|
|
64
|
+
config.jobs.tasks.push(buildPollCloseTask());
|
|
65
|
+
};
|
|
66
|
+
const canQueue = (payload) => typeof payload.jobs?.queue === "function";
|
|
67
|
+
/**
|
|
68
|
+
* Delete this form's pending (never-run) close jobs so a re-enqueue supersedes the prior one. Payload
|
|
69
|
+
* 3.85's `jobs.queue` has no concurrency key, so this mirrors core's `deleteScheduledPublishJobs`:
|
|
70
|
+
* a direct `db.deleteMany` on the jobs collection, scoped by task slug and the JSON `input.formId`.
|
|
71
|
+
* Best-effort; a failure here only risks a duplicate future job, which the idempotent handler absorbs.
|
|
72
|
+
*/
|
|
73
|
+
const deletePendingCloseJobs = async (payload, formId, req) => {
|
|
74
|
+
try {
|
|
75
|
+
await payload.db.deleteMany({
|
|
76
|
+
collection: JOBS_SLUG,
|
|
77
|
+
req,
|
|
78
|
+
where: { and: [
|
|
79
|
+
{ completedAt: { exists: false } },
|
|
80
|
+
{ processing: { equals: false } },
|
|
81
|
+
{ taskSlug: { equals: POLL_CLOSE_TASK_SLUG } },
|
|
82
|
+
{ "input.formId": { equals: String(formId) } }
|
|
83
|
+
] }
|
|
84
|
+
});
|
|
85
|
+
} catch (error) {
|
|
86
|
+
payload.logger?.error(`@10x-media/form-builder: failed to clear pending poll-close jobs for form ${String(formId)}: ${error instanceof Error ? error.message : String(error)}`);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Schedule (or reschedule) the auto-close for a poll after a save. Enqueues a `waitUntil: closesAt`
|
|
91
|
+
* job when the poll is enabled, has a `closesAt`, is still unresolved, and its strategy is not
|
|
92
|
+
* `manual`, and only when a job runner is present (`payload.jobs.queue`); otherwise the resolve-on-read
|
|
93
|
+
* fallback is the safety net. Any prior pending close job for the form is deleted first so the newest
|
|
94
|
+
* `closesAt` wins. Never throws; a queue failure is logged and swallowed.
|
|
95
|
+
*/
|
|
96
|
+
const enqueuePollClose = async (args) => {
|
|
97
|
+
const { payload, form, req } = args;
|
|
98
|
+
const poll = pollConfigOf(form.poll);
|
|
99
|
+
if (form.pollEnabled !== true || !poll) return;
|
|
100
|
+
const closesAt = typeof poll.closesAt === "string" && poll.closesAt.length > 0 ? poll.closesAt : void 0;
|
|
101
|
+
if (!closesAt || !hasEmptyWinners(poll) || strategyOf(poll) === "manual" || !canQueue(payload)) return;
|
|
102
|
+
const waitUntil = new Date(closesAt);
|
|
103
|
+
if (Number.isNaN(waitUntil.getTime())) return;
|
|
104
|
+
await deletePendingCloseJobs(payload, form.id, req);
|
|
105
|
+
try {
|
|
106
|
+
await payload.jobs.queue({
|
|
107
|
+
task: POLL_CLOSE_TASK_SLUG,
|
|
108
|
+
input: { formId: String(form.id) },
|
|
109
|
+
waitUntil,
|
|
110
|
+
req
|
|
111
|
+
});
|
|
112
|
+
} catch (error) {
|
|
113
|
+
payload.logger?.error(`@10x-media/form-builder: failed to enqueue poll-close for form ${String(form.id)}: ${error instanceof Error ? error.message : String(error)}`);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
//#endregion
|
|
117
|
+
export { POLL_CLOSE_TASK_SLUG, buildPollCloseTask, enqueuePollClose, registerPollCloseTask, runPollClose, shouldAutoResolvePoll };
|
|
118
|
+
|
|
119
|
+
//# sourceMappingURL=closeJob.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"closeJob.js","names":[],"sources":["../../src/poll/closeJob.ts"],"sourcesContent":["import type { Config, Payload, PayloadRequest, TaskConfig } from 'payload'\nimport { FORMS_SLUG } from '../collections/forms'\nimport { isPollClosed, type PollConfigLike, pollConfigOf } from '../form/pollState'\nimport { resolvePollOutcome } from './resolvePollOutcome'\n\nexport const POLL_CLOSE_TASK_SLUG = 'form-builder-poll-close'\n\n/** Payload's jobs collection; queried directly to supersede a superseded pending close job. */\nconst JOBS_SLUG = 'payload-jobs'\n\n/** Input the enqueue path stores; the handler re-loads everything else from the DB. */\nexport type PollCloseTaskInput = { formId: number | string }\n\n/** The slice of a loaded form the close gate reads. */\nexport type PollFormLike = { pollEnabled?: boolean | null; poll?: unknown }\n\nconst hasEmptyWinners = (poll: PollConfigLike): boolean => {\n\tconst winners = poll.outcome?.winningValues\n\treturn (\n\t\t!Array.isArray(winners) ||\n\t\twinners.filter((value) => typeof value === 'string' && value.length > 0).length === 0\n\t)\n}\n\nconst strategyOf = (poll: PollConfigLike): string =>\n\ttypeof poll.type === 'string' && poll.type.length > 0 ? poll.type : 'manual'\n\n/**\n * True when a poll's outcome should auto-resolve now: enabled, closed, still unresolved, and its\n * strategy is not `manual`. Shared by the close-job handler and the results-read fallback so the two\n * triggers apply the exact same gate.\n */\nexport const shouldAutoResolvePoll = (form: PollFormLike): boolean => {\n\tconst poll = pollConfigOf(form.poll)\n\treturn (\n\t\tform.pollEnabled === true &&\n\t\tpoll != null &&\n\t\tisPollClosed(poll) &&\n\t\thasEmptyWinners(poll) &&\n\t\tstrategyOf(poll) !== 'manual'\n\t)\n}\n\n/**\n * Run the scheduled close for one form. Idempotent: re-loads the form and only resolves when the\n * poll is enabled, closed, still unresolved, and not `manual`, so a re-run after the outcome was\n * recorded (or a job that fires slightly early) is a safe no-op. Tolerates a deleted form.\n */\nexport const runPollClose = async (args: {\n\tpayload: Payload\n\tinput: PollCloseTaskInput\n\treq?: PayloadRequest\n}): Promise<void> => {\n\tconst { payload, input, req } = args\n\tconst form = await payload\n\t\t.findByID({ collection: FORMS_SLUG, id: input.formId, depth: 0, overrideAccess: true, req })\n\t\t.catch(() => null)\n\tif (!form || !shouldAutoResolvePoll(form as PollFormLike)) {\n\t\treturn\n\t}\n\tawait resolvePollOutcome({ payload, formId: input.formId, req })\n}\n\n/** Native Payload jobs task that resolves a poll's outcome once it closes. */\nexport const buildPollCloseTask = (): TaskConfig =>\n\t({\n\t\tslug: POLL_CLOSE_TASK_SLUG,\n\t\tinputSchema: [{ name: 'formId', type: 'text', required: true }],\n\t\thandler: async ({ input, req }) => {\n\t\t\tawait runPollClose({ payload: req.payload, input: input as PollCloseTaskInput, req })\n\t\t\treturn { output: {} }\n\t\t},\n\t}) as TaskConfig\n\n/** Register the poll-close task on `config.jobs.tasks`, creating the jobs config if absent. */\nexport const registerPollCloseTask = (config: Config): void => {\n\tconfig.jobs ??= {}\n\tconfig.jobs.tasks ??= []\n\tconfig.jobs.tasks.push(buildPollCloseTask())\n}\n\nconst canQueue = (payload: Payload): boolean => typeof payload.jobs?.queue === 'function'\n\n/**\n * Delete this form's pending (never-run) close jobs so a re-enqueue supersedes the prior one. Payload\n * 3.85's `jobs.queue` has no concurrency key, so this mirrors core's `deleteScheduledPublishJobs`:\n * a direct `db.deleteMany` on the jobs collection, scoped by task slug and the JSON `input.formId`.\n * Best-effort; a failure here only risks a duplicate future job, which the idempotent handler absorbs.\n */\nconst deletePendingCloseJobs = async (\n\tpayload: Payload,\n\tformId: number | string,\n\treq?: PayloadRequest\n): Promise<void> => {\n\ttry {\n\t\tawait payload.db.deleteMany({\n\t\t\tcollection: JOBS_SLUG,\n\t\t\treq,\n\t\t\twhere: {\n\t\t\t\tand: [\n\t\t\t\t\t{ completedAt: { exists: false } },\n\t\t\t\t\t{ processing: { equals: false } },\n\t\t\t\t\t{ taskSlug: { equals: POLL_CLOSE_TASK_SLUG } },\n\t\t\t\t\t{ 'input.formId': { equals: String(formId) } },\n\t\t\t\t],\n\t\t\t},\n\t\t})\n\t} catch (error) {\n\t\tpayload.logger?.error(\n\t\t\t`@10x-media/form-builder: failed to clear pending poll-close jobs for form ${String(formId)}: ${\n\t\t\t\terror instanceof Error ? error.message : String(error)\n\t\t\t}`\n\t\t)\n\t}\n}\n\n/**\n * Schedule (or reschedule) the auto-close for a poll after a save. Enqueues a `waitUntil: closesAt`\n * job when the poll is enabled, has a `closesAt`, is still unresolved, and its strategy is not\n * `manual`, and only when a job runner is present (`payload.jobs.queue`); otherwise the resolve-on-read\n * fallback is the safety net. Any prior pending close job for the form is deleted first so the newest\n * `closesAt` wins. Never throws; a queue failure is logged and swallowed.\n */\nexport const enqueuePollClose = async (args: {\n\tpayload: Payload\n\tform: { id: number | string; pollEnabled?: boolean | null; poll?: unknown }\n\treq?: PayloadRequest\n}): Promise<void> => {\n\tconst { payload, form, req } = args\n\tconst poll = pollConfigOf(form.poll)\n\tif (form.pollEnabled !== true || !poll) {\n\t\treturn\n\t}\n\tconst closesAt =\n\t\ttypeof poll.closesAt === 'string' && poll.closesAt.length > 0 ? poll.closesAt : undefined\n\tif (!closesAt || !hasEmptyWinners(poll) || strategyOf(poll) === 'manual' || !canQueue(payload)) {\n\t\treturn\n\t}\n\tconst waitUntil = new Date(closesAt)\n\tif (Number.isNaN(waitUntil.getTime())) {\n\t\treturn\n\t}\n\tawait deletePendingCloseJobs(payload, form.id, req)\n\ttry {\n\t\tawait payload.jobs.queue({\n\t\t\ttask: POLL_CLOSE_TASK_SLUG,\n\t\t\tinput: { formId: String(form.id) },\n\t\t\twaitUntil,\n\t\t\treq,\n\t\t})\n\t} catch (error) {\n\t\tpayload.logger?.error(\n\t\t\t`@10x-media/form-builder: failed to enqueue poll-close for form ${String(form.id)}: ${\n\t\t\t\terror instanceof Error ? error.message : String(error)\n\t\t\t}`\n\t\t)\n\t}\n}\n"],"mappings":";;;;AAKA,MAAa,uBAAuB;;AAGpC,MAAM,YAAY;AAQlB,MAAM,mBAAmB,SAAkC;CAC1D,MAAM,UAAU,KAAK,SAAS;CAC9B,OACC,CAAC,MAAM,QAAQ,OAAO,KACtB,QAAQ,QAAQ,UAAU,OAAO,UAAU,YAAY,MAAM,SAAS,CAAC,EAAE,WAAW;AAEtF;AAEA,MAAM,cAAc,SACnB,OAAO,KAAK,SAAS,YAAY,KAAK,KAAK,SAAS,IAAI,KAAK,OAAO;;;;;;AAOrE,MAAa,yBAAyB,SAAgC;CACrE,MAAM,OAAO,aAAa,KAAK,IAAI;CACnC,OACC,KAAK,gBAAgB,QACrB,QAAQ,QACR,aAAa,IAAI,KACjB,gBAAgB,IAAI,KACpB,WAAW,IAAI,MAAM;AAEvB;;;;;;AAOA,MAAa,eAAe,OAAO,SAId;CACpB,MAAM,EAAE,SAAS,OAAO,QAAQ;CAChC,MAAM,OAAO,MAAM,QACjB,SAAS;EAAE,YAAY;EAAY,IAAI,MAAM;EAAQ,OAAO;EAAG,gBAAgB;EAAM;CAAI,CAAC,EAC1F,YAAY,IAAI;CAClB,IAAI,CAAC,QAAQ,CAAC,sBAAsB,IAAoB,GACvD;CAED,MAAM,mBAAmB;EAAE;EAAS,QAAQ,MAAM;EAAQ;CAAI,CAAC;AAChE;;AAGA,MAAa,4BACX;CACA,MAAM;CACN,aAAa,CAAC;EAAE,MAAM;EAAU,MAAM;EAAQ,UAAU;CAAK,CAAC;CAC9D,SAAS,OAAO,EAAE,OAAO,UAAU;EAClC,MAAM,aAAa;GAAE,SAAS,IAAI;GAAgB;GAA6B;EAAI,CAAC;EACpF,OAAO,EAAE,QAAQ,CAAC,EAAE;CACrB;AACD;;AAGD,MAAa,yBAAyB,WAAyB;CAC9D,OAAO,SAAS,CAAC;CACjB,OAAO,KAAK,UAAU,CAAC;CACvB,OAAO,KAAK,MAAM,KAAK,mBAAmB,CAAC;AAC5C;AAEA,MAAM,YAAY,YAA8B,OAAO,QAAQ,MAAM,UAAU;;;;;;;AAQ/E,MAAM,yBAAyB,OAC9B,SACA,QACA,QACmB;CACnB,IAAI;EACH,MAAM,QAAQ,GAAG,WAAW;GAC3B,YAAY;GACZ;GACA,OAAO,EACN,KAAK;IACJ,EAAE,aAAa,EAAE,QAAQ,MAAM,EAAE;IACjC,EAAE,YAAY,EAAE,QAAQ,MAAM,EAAE;IAChC,EAAE,UAAU,EAAE,QAAQ,qBAAqB,EAAE;IAC7C,EAAE,gBAAgB,EAAE,QAAQ,OAAO,MAAM,EAAE,EAAE;GAC9C,EACD;EACD,CAAC;CACF,SAAS,OAAO;EACf,QAAQ,QAAQ,MACf,6EAA6E,OAAO,MAAM,EAAE,IAC3F,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAEvD;CACD;AACD;;;;;;;;AASA,MAAa,mBAAmB,OAAO,SAIlB;CACpB,MAAM,EAAE,SAAS,MAAM,QAAQ;CAC/B,MAAM,OAAO,aAAa,KAAK,IAAI;CACnC,IAAI,KAAK,gBAAgB,QAAQ,CAAC,MACjC;CAED,MAAM,WACL,OAAO,KAAK,aAAa,YAAY,KAAK,SAAS,SAAS,IAAI,KAAK,WAAW,KAAA;CACjF,IAAI,CAAC,YAAY,CAAC,gBAAgB,IAAI,KAAK,WAAW,IAAI,MAAM,YAAY,CAAC,SAAS,OAAO,GAC5F;CAED,MAAM,YAAY,IAAI,KAAK,QAAQ;CACnC,IAAI,OAAO,MAAM,UAAU,QAAQ,CAAC,GACnC;CAED,MAAM,uBAAuB,SAAS,KAAK,IAAI,GAAG;CAClD,IAAI;EACH,MAAM,QAAQ,KAAK,MAAM;GACxB,MAAM;GACN,OAAO,EAAE,QAAQ,OAAO,KAAK,EAAE,EAAE;GACjC;GACA;EACD,CAAC;CACF,SAAS,OAAO;EACf,QAAQ,QAAQ,MACf,kEAAkE,OAAO,KAAK,EAAE,EAAE,IACjF,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAEvD;CACD;AACD"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Field, Payload, PayloadRequest } from "payload";
|
|
2
2
|
|
|
3
3
|
//#region src/poll/definePollOptionSource.d.ts
|
|
4
|
-
/** One selectable poll choice. `value` is the host's stable identifier contract: it is stored in submissions and later matched against `outcome.
|
|
4
|
+
/** One selectable poll choice. `value` is the host's stable identifier contract: it is stored in submissions and later matched against `outcome.winningValues`, so it must never change once votes exist. */
|
|
5
5
|
type PollOption = {
|
|
6
6
|
label: string;
|
|
7
7
|
value: string;
|
|
@@ -26,6 +26,15 @@ type PollOptionSource<TConfig extends Record<string, unknown> = Record<string, u
|
|
|
26
26
|
label: string;
|
|
27
27
|
config?: Field[];
|
|
28
28
|
resolve: (args: PollOptionResolveArgs<TConfig>) => Promise<PollOption[]> | PollOption[];
|
|
29
|
+
/**
|
|
30
|
+
* Optional outcome resolver: return the winning option value(s) from domain data (e.g. the
|
|
31
|
+
* recorded race result), a `string[]` when the result is a tie, or `undefined` while the outcome
|
|
32
|
+
* is not yet decidable. Backs the built-in `source` poll type, so a form whose `poll.type` is
|
|
33
|
+
* `source` auto-resolves through it (via the scheduled close job, a results-read, or an explicit
|
|
34
|
+
* `resolvePollOutcome({ payload, formId })`); every returned value is validated against the poll's
|
|
35
|
+
* resolved options before it is recorded, so each must be one of the values `resolve` yields.
|
|
36
|
+
*/
|
|
37
|
+
resolveOutcome?: (args: PollOptionResolveArgs<TConfig>) => Promise<string | string[] | undefined> | string | string[] | undefined;
|
|
29
38
|
};
|
|
30
39
|
/** Erased shape stored in the registry; config re-narrows per matched type at resolution. */
|
|
31
40
|
type AnyPollOptionSource = PollOptionSource<Record<string, unknown>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definePollOptionSource.js","names":[],"sources":["../../src/poll/definePollOptionSource.ts"],"sourcesContent":["import type { Field, Payload, PayloadRequest } from 'payload'\n\n/** One selectable poll choice. `value` is the host's stable identifier contract: it is stored in submissions and later matched against `outcome.
|
|
1
|
+
{"version":3,"file":"definePollOptionSource.js","names":[],"sources":["../../src/poll/definePollOptionSource.ts"],"sourcesContent":["import type { Field, Payload, PayloadRequest } from 'payload'\n\n/** One selectable poll choice. `value` is the host's stable identifier contract: it is stored in submissions and later matched against `outcome.winningValues`, so it must never change once votes exist. */\nexport type PollOption = { label: string; value: string }\n\nexport type PollOptionResolveArgs<\n\tTConfig extends Record<string, unknown> = Record<string, unknown>,\n> = {\n\tconfig: TConfig\n\tform: { id: number | string; title?: string }\n\tpayload: Payload\n\treq?: PayloadRequest\n}\n\n/**\n * A poll option source type, authored once by the host: `config` is the admin `Field[]` an author\n * fills per form (e.g. an event id); `resolve` maps that config to the current options from host\n * domain data. Called at render time (via `resolvePollOptions`) and again at submission time, where\n * the resolved values are the only accepted answers (fail closed).\n */\nexport type PollOptionSource<TConfig extends Record<string, unknown> = Record<string, unknown>> = {\n\ttype: string\n\tlabel: string\n\tconfig?: Field[]\n\tresolve: (args: PollOptionResolveArgs<TConfig>) => Promise<PollOption[]> | PollOption[]\n\t/**\n\t * Optional outcome resolver: return the winning option value(s) from domain data (e.g. the\n\t * recorded race result), a `string[]` when the result is a tie, or `undefined` while the outcome\n\t * is not yet decidable. Backs the built-in `source` poll type, so a form whose `poll.type` is\n\t * `source` auto-resolves through it (via the scheduled close job, a results-read, or an explicit\n\t * `resolvePollOutcome({ payload, formId })`); every returned value is validated against the poll's\n\t * resolved options before it is recorded, so each must be one of the values `resolve` yields.\n\t */\n\tresolveOutcome?: (\n\t\targs: PollOptionResolveArgs<TConfig>\n\t) => Promise<string | string[] | undefined> | string | string[] | undefined\n}\n\n/** Erased shape stored in the registry; config re-narrows per matched type at resolution. */\nexport type AnyPollOptionSource = PollOptionSource<Record<string, unknown>>\n\nexport const definePollOptionSource = <\n\tTConfig extends Record<string, unknown> = Record<string, unknown>,\n>(\n\tsource: PollOptionSource<TConfig>\n): PollOptionSource<TConfig> => source\n"],"mappings":";AAyCA,MAAa,0BAGZ,WAC+B"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { FieldAggregation } from "../aggregation/types.js";
|
|
2
|
+
import { PollConfigLike } from "../form/pollState.js";
|
|
3
|
+
import { Field, Payload, PayloadRequest } from "payload";
|
|
4
|
+
|
|
5
|
+
//#region src/poll/definePollType.d.ts
|
|
6
|
+
type PollOutcomeStrategyArgs = {
|
|
7
|
+
payload: Payload;
|
|
8
|
+
req?: PayloadRequest; /** The loaded forms document, widened so a strategy can read host-owned members (e.g. `form.tenant`). */
|
|
9
|
+
form: {
|
|
10
|
+
id: number | string;
|
|
11
|
+
poll?: unknown;
|
|
12
|
+
fields?: unknown;
|
|
13
|
+
} & Record<string, unknown>; /** The poll config narrowed from the form via `pollConfigOf`. */
|
|
14
|
+
poll: PollConfigLike; /** Aggregate the poll's results field, memoized by the caller. Null when there is no results field or no data. */
|
|
15
|
+
aggregate: () => Promise<FieldAggregation | null>;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* An outcome-resolution strategy for a poll, authored once and registered via the plugin `poll.types`
|
|
19
|
+
* option (built-ins `manual`, `mostVoted`, `source` are always registered). The poll's `type` select
|
|
20
|
+
* picks one; `resolvePollOutcome` runs it when no explicit `winningValues` are passed. `resolveOutcome`
|
|
21
|
+
* returns the winning value set, or `undefined` when the outcome is not yet decidable so nothing is
|
|
22
|
+
* written. Every returned value is still validated against the poll's effective options by
|
|
23
|
+
* `pollOutcomeBeforeChange`, so a strategy can never record a value outside the poll's choices.
|
|
24
|
+
*/
|
|
25
|
+
type PollOutcomeStrategy = {
|
|
26
|
+
type: string; /** i18n-key or literal (resolved like a field label), or a per-locale record. */
|
|
27
|
+
label: string | Record<string, string>;
|
|
28
|
+
config?: Field[];
|
|
29
|
+
resolveOutcome: (args: PollOutcomeStrategyArgs) => Promise<string[] | undefined> | string[] | undefined;
|
|
30
|
+
};
|
|
31
|
+
declare const definePollType: (strategy: PollOutcomeStrategy) => PollOutcomeStrategy;
|
|
32
|
+
//#endregion
|
|
33
|
+
export { PollOutcomeStrategy, PollOutcomeStrategyArgs, definePollType };
|
|
34
|
+
//# sourceMappingURL=definePollType.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"definePollType.js","names":[],"sources":["../../src/poll/definePollType.ts"],"sourcesContent":["import type { Field, Payload, PayloadRequest } from 'payload'\nimport type { FieldAggregation } from '../aggregation/types'\nimport type { PollConfigLike } from '../form/pollState'\n\nexport type PollOutcomeStrategyArgs = {\n\tpayload: Payload\n\treq?: PayloadRequest\n\t/** The loaded forms document, widened so a strategy can read host-owned members (e.g. `form.tenant`). */\n\tform: { id: number | string; poll?: unknown; fields?: unknown } & Record<string, unknown>\n\t/** The poll config narrowed from the form via `pollConfigOf`. */\n\tpoll: PollConfigLike\n\t/** Aggregate the poll's results field, memoized by the caller. Null when there is no results field or no data. */\n\taggregate: () => Promise<FieldAggregation | null>\n}\n\n/**\n * An outcome-resolution strategy for a poll, authored once and registered via the plugin `poll.types`\n * option (built-ins `manual`, `mostVoted`, `source` are always registered). The poll's `type` select\n * picks one; `resolvePollOutcome` runs it when no explicit `winningValues` are passed. `resolveOutcome`\n * returns the winning value set, or `undefined` when the outcome is not yet decidable so nothing is\n * written. Every returned value is still validated against the poll's effective options by\n * `pollOutcomeBeforeChange`, so a strategy can never record a value outside the poll's choices.\n */\nexport type PollOutcomeStrategy = {\n\ttype: string\n\t/** i18n-key or literal (resolved like a field label), or a per-locale record. */\n\tlabel: string | Record<string, string>\n\tconfig?: Field[]\n\tresolveOutcome: (\n\t\targs: PollOutcomeStrategyArgs\n\t) => Promise<string[] | undefined> | string[] | undefined\n}\n\nexport const definePollType = (strategy: PollOutcomeStrategy): PollOutcomeStrategy => strategy\n"],"mappings":";AAiCA,MAAa,kBAAkB,aAAuD"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { PollOption } from "./definePollOptionSource.js";
|
|
2
|
+
import { FieldTypeRegistry } from "../fields/registry.js";
|
|
3
|
+
import { PollOptionSourceRegistry } from "./registry.js";
|
|
4
|
+
import { Payload, PayloadRequest } from "payload";
|
|
5
|
+
|
|
6
|
+
//#region src/poll/effectivePollOptions.d.ts
|
|
7
|
+
type ResolveEffectivePollOptionsArgs = {
|
|
8
|
+
payload: Payload;
|
|
9
|
+
req?: PayloadRequest; /** A forms document (or an equivalent merged view of one during a save). */
|
|
10
|
+
form: {
|
|
11
|
+
id: number | string;
|
|
12
|
+
title?: string | null; /** Top-level poll-enable flag; the poll config (results field, source) stays under `poll`. */
|
|
13
|
+
pollEnabled?: boolean | null;
|
|
14
|
+
poll?: unknown;
|
|
15
|
+
fields?: unknown;
|
|
16
|
+
}; /** Registry override for plugin-internal callers; defaults to the registry stashed on the config. */
|
|
17
|
+
sources?: PollOptionSourceRegistry;
|
|
18
|
+
/**
|
|
19
|
+
* Field-type registry override; defaults to the one stashed on the config. Lets the results
|
|
20
|
+
* field's definition source its own options via {@link FormFieldDefinition.resolveOptions}.
|
|
21
|
+
*/
|
|
22
|
+
fieldTypes?: FieldTypeRegistry;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* The poll's currently valid choice set, resolved in a fixed precedence: source-resolved options
|
|
26
|
+
* when the poll has an `optionSource`, else the results field type's own {@link
|
|
27
|
+
* FormFieldDefinition.resolveOptions} when it declares one (given the live field instance so a
|
|
28
|
+
* custom type can enumerate the values it holds), else the authored options of the field instance.
|
|
29
|
+
* Empty when the poll is disabled, no results field is set, or none of the branches yields options.
|
|
30
|
+
* This single set backs both the admin winner select (the `/:id/poll-options` endpoint) and outcome
|
|
31
|
+
* membership validation, so what the admin offers and what the server accepts cannot drift. Source
|
|
32
|
+
* and resolver failures propagate so callers fail closed; the resolver branch is cached per request
|
|
33
|
+
* (keyed by form id) when a `req` is provided, matching the source path.
|
|
34
|
+
*/
|
|
35
|
+
declare const resolveEffectivePollOptions: (args: ResolveEffectivePollOptionsArgs) => Promise<PollOption[]>;
|
|
36
|
+
//#endregion
|
|
37
|
+
export { ResolveEffectivePollOptionsArgs, resolveEffectivePollOptions };
|
|
38
|
+
//# sourceMappingURL=effectivePollOptions.d.ts.map
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { fieldTypesOf } from "../fields/registry.js";
|
|
2
|
+
import { pollConfigOf } from "../form/pollState.js";
|
|
3
|
+
import { instanceOptionsOf } from "../fields/instanceOptions.js";
|
|
4
|
+
import { resolvePollOptions } from "./resolvePollOptions.js";
|
|
5
|
+
//#region src/poll/effectivePollOptions.ts
|
|
6
|
+
const CACHE_KEY = "formBuilderFieldOptionsCache";
|
|
7
|
+
const cacheOf = (req) => {
|
|
8
|
+
if (!req?.context) return;
|
|
9
|
+
const existing = req.context[CACHE_KEY];
|
|
10
|
+
if (existing instanceof Map) return existing;
|
|
11
|
+
const created = /* @__PURE__ */ new Map();
|
|
12
|
+
req.context[CACHE_KEY] = created;
|
|
13
|
+
return created;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* The poll's currently valid choice set, resolved in a fixed precedence: source-resolved options
|
|
17
|
+
* when the poll has an `optionSource`, else the results field type's own {@link
|
|
18
|
+
* FormFieldDefinition.resolveOptions} when it declares one (given the live field instance so a
|
|
19
|
+
* custom type can enumerate the values it holds), else the authored options of the field instance.
|
|
20
|
+
* Empty when the poll is disabled, no results field is set, or none of the branches yields options.
|
|
21
|
+
* This single set backs both the admin winner select (the `/:id/poll-options` endpoint) and outcome
|
|
22
|
+
* membership validation, so what the admin offers and what the server accepts cannot drift. Source
|
|
23
|
+
* and resolver failures propagate so callers fail closed; the resolver branch is cached per request
|
|
24
|
+
* (keyed by form id) when a `req` is provided, matching the source path.
|
|
25
|
+
*/
|
|
26
|
+
const resolveEffectivePollOptions = async (args) => {
|
|
27
|
+
const { payload, req, form, sources, fieldTypes } = args;
|
|
28
|
+
const poll = pollConfigOf(form.poll);
|
|
29
|
+
if (form.pollEnabled !== true || !poll) return [];
|
|
30
|
+
const sourced = await resolvePollOptions({
|
|
31
|
+
payload,
|
|
32
|
+
req,
|
|
33
|
+
form,
|
|
34
|
+
sources
|
|
35
|
+
});
|
|
36
|
+
if (sourced) return sourced;
|
|
37
|
+
const resultsField = typeof poll.resultsField === "string" && poll.resultsField.length > 0 ? poll.resultsField : void 0;
|
|
38
|
+
if (!resultsField) return [];
|
|
39
|
+
const instance = (Array.isArray(form.fields) ? form.fields : []).find((entry) => entry.name === resultsField);
|
|
40
|
+
if (!instance) return [];
|
|
41
|
+
const definition = (fieldTypes ?? fieldTypesOf(payload)).get(instance.blockType);
|
|
42
|
+
if (definition?.resolveOptions) {
|
|
43
|
+
const cache = cacheOf(req);
|
|
44
|
+
const cacheKey = String(form.id);
|
|
45
|
+
const cached = cache?.get(cacheKey);
|
|
46
|
+
if (cached) return cached;
|
|
47
|
+
const options = await definition.resolveOptions({
|
|
48
|
+
instance,
|
|
49
|
+
form: {
|
|
50
|
+
id: form.id,
|
|
51
|
+
title: typeof form.title === "string" ? form.title : void 0
|
|
52
|
+
},
|
|
53
|
+
payload,
|
|
54
|
+
req
|
|
55
|
+
});
|
|
56
|
+
cache?.set(cacheKey, options);
|
|
57
|
+
return options;
|
|
58
|
+
}
|
|
59
|
+
return instanceOptionsOf(instance) ?? [];
|
|
60
|
+
};
|
|
61
|
+
//#endregion
|
|
62
|
+
export { resolveEffectivePollOptions };
|
|
63
|
+
|
|
64
|
+
//# sourceMappingURL=effectivePollOptions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"effectivePollOptions.js","names":[],"sources":["../../src/poll/effectivePollOptions.ts"],"sourcesContent":["import type { Payload, PayloadRequest } from 'payload'\nimport { instanceOptionsOf } from '../fields/instanceOptions'\nimport { type FieldTypeRegistry, fieldTypesOf } from '../fields/registry'\nimport { pollConfigOf } from '../form/pollState'\nimport type { FormFieldInstance } from '../submissions/types'\nimport type { PollOption } from './definePollOptionSource'\nimport type { PollOptionSourceRegistry } from './registry'\nimport { resolvePollOptions } from './resolvePollOptions'\n\nexport type ResolveEffectivePollOptionsArgs = {\n\tpayload: Payload\n\treq?: PayloadRequest\n\t/** A forms document (or an equivalent merged view of one during a save). */\n\tform: {\n\t\tid: number | string\n\t\ttitle?: string | null\n\t\t/** Top-level poll-enable flag; the poll config (results field, source) stays under `poll`. */\n\t\tpollEnabled?: boolean | null\n\t\tpoll?: unknown\n\t\tfields?: unknown\n\t}\n\t/** Registry override for plugin-internal callers; defaults to the registry stashed on the config. */\n\tsources?: PollOptionSourceRegistry\n\t/**\n\t * Field-type registry override; defaults to the one stashed on the config. Lets the results\n\t * field's definition source its own options via {@link FormFieldDefinition.resolveOptions}.\n\t */\n\tfieldTypes?: FieldTypeRegistry\n}\n\nconst CACHE_KEY = 'formBuilderFieldOptionsCache'\n\ntype OptionsCache = Map<string, PollOption[]>\n\nconst cacheOf = (req: PayloadRequest | undefined): OptionsCache | undefined => {\n\tif (!req?.context) {\n\t\treturn undefined\n\t}\n\tconst existing = req.context[CACHE_KEY]\n\tif (existing instanceof Map) {\n\t\treturn existing as OptionsCache\n\t}\n\tconst created: OptionsCache = new Map()\n\treq.context[CACHE_KEY] = created\n\treturn created\n}\n\n/**\n * The poll's currently valid choice set, resolved in a fixed precedence: source-resolved options\n * when the poll has an `optionSource`, else the results field type's own {@link\n * FormFieldDefinition.resolveOptions} when it declares one (given the live field instance so a\n * custom type can enumerate the values it holds), else the authored options of the field instance.\n * Empty when the poll is disabled, no results field is set, or none of the branches yields options.\n * This single set backs both the admin winner select (the `/:id/poll-options` endpoint) and outcome\n * membership validation, so what the admin offers and what the server accepts cannot drift. Source\n * and resolver failures propagate so callers fail closed; the resolver branch is cached per request\n * (keyed by form id) when a `req` is provided, matching the source path.\n */\nexport const resolveEffectivePollOptions = async (\n\targs: ResolveEffectivePollOptionsArgs\n): Promise<PollOption[]> => {\n\tconst { payload, req, form, sources, fieldTypes } = args\n\tconst poll = pollConfigOf(form.poll)\n\tif (form.pollEnabled !== true || !poll) {\n\t\treturn []\n\t}\n\tconst sourced = await resolvePollOptions({ payload, req, form, sources })\n\tif (sourced) {\n\t\treturn sourced\n\t}\n\tconst resultsField =\n\t\ttypeof poll.resultsField === 'string' && poll.resultsField.length > 0\n\t\t\t? poll.resultsField\n\t\t\t: undefined\n\tif (!resultsField) {\n\t\treturn []\n\t}\n\tconst instances = Array.isArray(form.fields) ? (form.fields as FormFieldInstance[]) : []\n\tconst instance = instances.find((entry) => entry.name === resultsField)\n\tif (!instance) {\n\t\treturn []\n\t}\n\tconst definition = (fieldTypes ?? fieldTypesOf(payload)).get(instance.blockType)\n\tif (definition?.resolveOptions) {\n\t\tconst cache = cacheOf(req)\n\t\tconst cacheKey = String(form.id)\n\t\tconst cached = cache?.get(cacheKey)\n\t\tif (cached) {\n\t\t\treturn cached\n\t\t}\n\t\tconst options = await definition.resolveOptions({\n\t\t\tinstance,\n\t\t\tform: { id: form.id, title: typeof form.title === 'string' ? form.title : undefined },\n\t\t\tpayload,\n\t\t\treq,\n\t\t})\n\t\tcache?.set(cacheKey, options)\n\t\treturn options\n\t}\n\treturn instanceOptionsOf(instance) ?? []\n}\n"],"mappings":";;;;;AA8BA,MAAM,YAAY;AAIlB,MAAM,WAAW,QAA8D;CAC9E,IAAI,CAAC,KAAK,SACT;CAED,MAAM,WAAW,IAAI,QAAQ;CAC7B,IAAI,oBAAoB,KACvB,OAAO;CAER,MAAM,0BAAwB,IAAI,IAAI;CACtC,IAAI,QAAQ,aAAa;CACzB,OAAO;AACR;;;;;;;;;;;;AAaA,MAAa,8BAA8B,OAC1C,SAC2B;CAC3B,MAAM,EAAE,SAAS,KAAK,MAAM,SAAS,eAAe;CACpD,MAAM,OAAO,aAAa,KAAK,IAAI;CACnC,IAAI,KAAK,gBAAgB,QAAQ,CAAC,MACjC,OAAO,CAAC;CAET,MAAM,UAAU,MAAM,mBAAmB;EAAE;EAAS;EAAK;EAAM;CAAQ,CAAC;CACxE,IAAI,SACH,OAAO;CAER,MAAM,eACL,OAAO,KAAK,iBAAiB,YAAY,KAAK,aAAa,SAAS,IACjE,KAAK,eACL,KAAA;CACJ,IAAI,CAAC,cACJ,OAAO,CAAC;CAGT,MAAM,YADY,MAAM,QAAQ,KAAK,MAAM,IAAK,KAAK,SAAiC,CAAC,GAC5D,MAAM,UAAU,MAAM,SAAS,YAAY;CACtE,IAAI,CAAC,UACJ,OAAO,CAAC;CAET,MAAM,cAAc,cAAc,aAAa,OAAO,GAAG,IAAI,SAAS,SAAS;CAC/E,IAAI,YAAY,gBAAgB;EAC/B,MAAM,QAAQ,QAAQ,GAAG;EACzB,MAAM,WAAW,OAAO,KAAK,EAAE;EAC/B,MAAM,SAAS,OAAO,IAAI,QAAQ;EAClC,IAAI,QACH,OAAO;EAER,MAAM,UAAU,MAAM,WAAW,eAAe;GAC/C;GACA,MAAM;IAAE,IAAI,KAAK;IAAI,OAAO,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ,KAAA;GAAU;GACpF;GACA;EACD,CAAC;EACD,OAAO,IAAI,UAAU,OAAO;EAC5B,OAAO;CACR;CACA,OAAO,kBAAkB,QAAQ,KAAK,CAAC;AACxC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { PollOutcomeStrategy } from "./definePollType.js";
|
|
2
|
+
|
|
3
|
+
//#region src/poll/mostVoted.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Every bucket value tied for the highest count, so a draw records all co-winners. Empty when there
|
|
6
|
+
* are no buckets. Pure: the caller decides whether the aggregation is trustworthy enough to act on.
|
|
7
|
+
*/
|
|
8
|
+
declare const topBucketValues: (buckets: {
|
|
9
|
+
value: string;
|
|
10
|
+
count: number;
|
|
11
|
+
}[]) => string[];
|
|
12
|
+
/**
|
|
13
|
+
* Auto-resolve to whichever choice(s) received the most votes. Refuses to decide, returning
|
|
14
|
+
* `undefined` (no write), when there is no aggregation, no responses yet, or the aggregation was
|
|
15
|
+
* `truncated` (a sample cannot be trusted to name the true winner, so it is flagged for a manual
|
|
16
|
+
* call instead). A perfect tie records every co-winner.
|
|
17
|
+
*/
|
|
18
|
+
declare const mostVotedStrategy: PollOutcomeStrategy;
|
|
19
|
+
//#endregion
|
|
20
|
+
export { mostVotedStrategy, topBucketValues };
|
|
21
|
+
//# sourceMappingURL=mostVoted.d.ts.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { keys } from "../translations/keys.js";
|
|
2
|
+
import { definePollType } from "./definePollType.js";
|
|
3
|
+
//#region src/poll/mostVoted.ts
|
|
4
|
+
/**
|
|
5
|
+
* Every bucket value tied for the highest count, so a draw records all co-winners. Empty when there
|
|
6
|
+
* are no buckets. Pure: the caller decides whether the aggregation is trustworthy enough to act on.
|
|
7
|
+
*/
|
|
8
|
+
const topBucketValues = (buckets) => {
|
|
9
|
+
if (buckets.length === 0) return [];
|
|
10
|
+
const max = Math.max(...buckets.map((bucket) => bucket.count));
|
|
11
|
+
return buckets.filter((bucket) => bucket.count === max).map((bucket) => bucket.value);
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Auto-resolve to whichever choice(s) received the most votes. Refuses to decide, returning
|
|
15
|
+
* `undefined` (no write), when there is no aggregation, no responses yet, or the aggregation was
|
|
16
|
+
* `truncated` (a sample cannot be trusted to name the true winner, so it is flagged for a manual
|
|
17
|
+
* call instead). A perfect tie records every co-winner.
|
|
18
|
+
*/
|
|
19
|
+
const mostVotedStrategy = definePollType({
|
|
20
|
+
type: "mostVoted",
|
|
21
|
+
label: keys.pollTypeMostVoted,
|
|
22
|
+
resolveOutcome: async ({ aggregate }) => {
|
|
23
|
+
const aggregation = await aggregate();
|
|
24
|
+
if (!aggregation || aggregation.total === 0 || aggregation.truncated) return;
|
|
25
|
+
const winners = topBucketValues(aggregation.buckets);
|
|
26
|
+
return winners.length > 0 ? winners : void 0;
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
//#endregion
|
|
30
|
+
export { mostVotedStrategy, topBucketValues };
|
|
31
|
+
|
|
32
|
+
//# sourceMappingURL=mostVoted.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mostVoted.js","names":[],"sources":["../../src/poll/mostVoted.ts"],"sourcesContent":["import { keys } from '../translations/keys'\nimport { definePollType, type PollOutcomeStrategy } from './definePollType'\n\n/**\n * Every bucket value tied for the highest count, so a draw records all co-winners. Empty when there\n * are no buckets. Pure: the caller decides whether the aggregation is trustworthy enough to act on.\n */\nexport const topBucketValues = (buckets: { value: string; count: number }[]): string[] => {\n\tif (buckets.length === 0) {\n\t\treturn []\n\t}\n\tconst max = Math.max(...buckets.map((bucket) => bucket.count))\n\treturn buckets.filter((bucket) => bucket.count === max).map((bucket) => bucket.value)\n}\n\n/**\n * Auto-resolve to whichever choice(s) received the most votes. Refuses to decide, returning\n * `undefined` (no write), when there is no aggregation, no responses yet, or the aggregation was\n * `truncated` (a sample cannot be trusted to name the true winner, so it is flagged for a manual\n * call instead). A perfect tie records every co-winner.\n */\nexport const mostVotedStrategy: PollOutcomeStrategy = definePollType({\n\ttype: 'mostVoted',\n\tlabel: keys.pollTypeMostVoted,\n\tresolveOutcome: async ({ aggregate }) => {\n\t\tconst aggregation = await aggregate()\n\t\tif (!aggregation || aggregation.total === 0 || aggregation.truncated) {\n\t\t\treturn undefined\n\t\t}\n\t\tconst winners = topBucketValues(aggregation.buckets)\n\t\treturn winners.length > 0 ? winners : undefined\n\t},\n})\n"],"mappings":";;;;;;;AAOA,MAAa,mBAAmB,YAA0D;CACzF,IAAI,QAAQ,WAAW,GACtB,OAAO,CAAC;CAET,MAAM,MAAM,KAAK,IAAI,GAAG,QAAQ,KAAK,WAAW,OAAO,KAAK,CAAC;CAC7D,OAAO,QAAQ,QAAQ,WAAW,OAAO,UAAU,GAAG,EAAE,KAAK,WAAW,OAAO,KAAK;AACrF;;;;;;;AAQA,MAAa,oBAAyC,eAAe;CACpE,MAAM;CACN,OAAO,KAAK;CACZ,gBAAgB,OAAO,EAAE,gBAAgB;EACxC,MAAM,cAAc,MAAM,UAAU;EACpC,IAAI,CAAC,eAAe,YAAY,UAAU,KAAK,YAAY,WAC1D;EAED,MAAM,UAAU,gBAAgB,YAAY,OAAO;EACnD,OAAO,QAAQ,SAAS,IAAI,UAAU,KAAA;CACvC;AACD,CAAC"}
|