@10x-media/form-builder 0.1.0-beta.3 → 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.
Files changed (406) hide show
  1. package/CHANGELOG.md +125 -0
  2. package/README.md +4 -4
  3. package/dist/actions/body/serializeBody.d.ts +22 -4
  4. package/dist/actions/body/serializeBody.js +3 -1
  5. package/dist/actions/body/serializeBody.js.map +1 -1
  6. package/dist/actions/body/textOfBody.d.ts +13 -0
  7. package/dist/actions/body/textOfBody.js +35 -0
  8. package/dist/actions/body/textOfBody.js.map +1 -0
  9. package/dist/actions/builtin/confirmation.js +51 -12
  10. package/dist/actions/builtin/confirmation.js.map +1 -1
  11. package/dist/actions/builtin/emailTeam.js +82 -34
  12. package/dist/actions/builtin/emailTeam.js.map +1 -1
  13. package/dist/actions/builtin/index.d.ts +18 -1
  14. package/dist/actions/builtin/index.js +21 -7
  15. package/dist/actions/builtin/index.js.map +1 -1
  16. package/dist/actions/builtin/signedWebhook.js +2 -16
  17. package/dist/actions/builtin/signedWebhook.js.map +1 -1
  18. package/dist/actions/defineAction.d.ts +1 -1
  19. package/dist/actions/fromAddresses.d.ts +21 -0
  20. package/dist/actions/fromAddresses.js +74 -0
  21. package/dist/actions/fromAddresses.js.map +1 -0
  22. package/dist/actions/runActions.js +2 -0
  23. package/dist/actions/runActions.js.map +1 -1
  24. package/dist/actions/task.js +9 -8
  25. package/dist/actions/task.js.map +1 -1
  26. package/dist/aggregation/aggregateResponses.d.ts +9 -0
  27. package/dist/aggregation/aggregateResponses.js +8 -14
  28. package/dist/aggregation/aggregateResponses.js.map +1 -1
  29. package/dist/aggregation/resolveResultsRequest.d.ts +35 -6
  30. package/dist/aggregation/resolveResultsRequest.js +68 -11
  31. package/dist/aggregation/resolveResultsRequest.js.map +1 -1
  32. package/dist/calc/computeCalcFields.js +2 -1
  33. package/dist/calc/computeCalcFields.js.map +1 -1
  34. package/dist/client/ByteSizeField.d.ts +8 -0
  35. package/dist/client/ByteSizeField.js +3 -1
  36. package/dist/client/ByteSizeField.js.map +1 -1
  37. package/dist/client/ClosePollButton.d.ts +15 -0
  38. package/dist/client/ClosePollButton.js +80 -0
  39. package/dist/client/ClosePollButton.js.map +1 -0
  40. package/dist/client/CondensedArray.d.ts +15 -0
  41. package/dist/client/CondensedArray.js +97 -0
  42. package/dist/client/CondensedArray.js.map +1 -0
  43. package/dist/client/EndpointOptionsSelect.d.ts +45 -0
  44. package/dist/client/EndpointOptionsSelect.js +103 -0
  45. package/dist/client/EndpointOptionsSelect.js.map +1 -0
  46. package/dist/client/FieldBlockLabel.d.ts +18 -0
  47. package/dist/client/FieldBlockLabel.js +34 -0
  48. package/dist/client/FieldBlockLabel.js.map +1 -0
  49. package/dist/client/FieldNameSelect.d.ts +20 -4
  50. package/dist/client/FieldNameSelect.js +25 -14
  51. package/dist/client/FieldNameSelect.js.map +1 -1
  52. package/dist/client/FlowBuilder.d.ts +2 -1
  53. package/dist/client/FlowBuilder.js +134 -83
  54. package/dist/client/FlowBuilder.js.map +1 -1
  55. package/dist/client/RuleDescription.d.ts +21 -0
  56. package/dist/client/RuleDescription.js +19 -0
  57. package/dist/client/RuleDescription.js.map +1 -0
  58. package/dist/client/admin.css +83 -5
  59. package/dist/client/endpointOptions.js +29 -0
  60. package/dist/client/endpointOptions.js.map +1 -0
  61. package/dist/client/flowAuthoring.js +142 -0
  62. package/dist/client/flowAuthoring.js.map +1 -0
  63. package/dist/client/synthesizeClientField.js +21 -10
  64. package/dist/client/synthesizeClientField.js.map +1 -1
  65. package/dist/collections/buttonFields.d.ts +34 -0
  66. package/dist/collections/buttonFields.js +35 -0
  67. package/dist/collections/buttonFields.js.map +1 -0
  68. package/dist/collections/cells/FieldCountCell.d.ts +15 -0
  69. package/dist/collections/cells/FieldCountCell.js +18 -0
  70. package/dist/collections/cells/FieldCountCell.js.map +1 -0
  71. package/dist/collections/cells/FlowStepsCell.d.ts +14 -0
  72. package/dist/collections/cells/FlowStepsCell.js +17 -0
  73. package/dist/collections/cells/FlowStepsCell.js.map +1 -0
  74. package/dist/collections/formSubmissions.js +55 -12
  75. package/dist/collections/formSubmissions.js.map +1 -1
  76. package/dist/collections/forms.js +376 -57
  77. package/dist/collections/forms.js.map +1 -1
  78. package/dist/collections/redirectFields.d.ts +29 -0
  79. package/dist/conditions/conditionType.js +9 -2
  80. package/dist/conditions/conditionType.js.map +1 -1
  81. package/dist/conditions/normalizeConditions.js +2 -1
  82. package/dist/conditions/normalizeConditions.js.map +1 -1
  83. package/dist/consent/applyConsentStatements.d.ts +14 -0
  84. package/dist/consent/applyConsentStatements.js +17 -0
  85. package/dist/consent/applyConsentStatements.js.map +1 -0
  86. package/dist/consent/captureConsent.d.ts +19 -8
  87. package/dist/consent/captureConsent.js +34 -11
  88. package/dist/consent/captureConsent.js.map +1 -1
  89. package/dist/consent/consentSourcesField.d.ts +58 -0
  90. package/dist/consent/consentSourcesField.js +79 -0
  91. package/dist/consent/consentSourcesField.js.map +1 -0
  92. package/dist/consent/resolveConsentEntries.d.ts +24 -0
  93. package/dist/consent/resolveConsentEntries.js +43 -0
  94. package/dist/consent/resolveConsentEntries.js.map +1 -0
  95. package/dist/consent/resolveConsentSourcesRequest.d.ts +37 -0
  96. package/dist/consent/resolveConsentSourcesRequest.js +55 -0
  97. package/dist/consent/resolveConsentSourcesRequest.js.map +1 -0
  98. package/dist/consent/resolveConsentStatements.d.ts +45 -0
  99. package/dist/consent/resolveConsentStatements.js +44 -0
  100. package/dist/consent/resolveConsentStatements.js.map +1 -0
  101. package/dist/consent/resolvePublishedVersionRef.d.ts +17 -11
  102. package/dist/consent/resolvePublishedVersionRef.js +13 -10
  103. package/dist/consent/resolvePublishedVersionRef.js.map +1 -1
  104. package/dist/consent/types.d.ts +49 -0
  105. package/dist/email/departments.d.ts +55 -0
  106. package/dist/email/departments.js +130 -0
  107. package/dist/email/departments.js.map +1 -0
  108. package/dist/email/departmentsField.d.ts +49 -0
  109. package/dist/email/departmentsField.js +69 -0
  110. package/dist/email/departmentsField.js.map +1 -0
  111. package/dist/exports/client.d.ts +8 -1
  112. package/dist/exports/client.js +8 -1
  113. package/dist/exports/react.d.ts +15 -6
  114. package/dist/exports/react.js +11 -3
  115. package/dist/exports/rsc.d.ts +8 -1
  116. package/dist/exports/rsc.js +8 -1
  117. package/dist/exports/types.d.ts +18 -10
  118. package/dist/fields/buildFieldBlocks.js +43 -19
  119. package/dist/fields/buildFieldBlocks.js.map +1 -1
  120. package/dist/fields/builtin/calculation.js +7 -0
  121. package/dist/fields/builtin/calculation.js.map +1 -1
  122. package/dist/fields/builtin/checkbox.js +8 -0
  123. package/dist/fields/builtin/checkbox.js.map +1 -1
  124. package/dist/fields/builtin/consent.js +25 -15
  125. package/dist/fields/builtin/consent.js.map +1 -1
  126. package/dist/fields/builtin/country.d.ts +13 -0
  127. package/dist/fields/builtin/country.js +30 -0
  128. package/dist/fields/builtin/country.js.map +1 -0
  129. package/dist/fields/builtin/date.js +2 -0
  130. package/dist/fields/builtin/date.js.map +1 -1
  131. package/dist/fields/builtin/file.js +13 -8
  132. package/dist/fields/builtin/file.js.map +1 -1
  133. package/dist/fields/builtin/index.d.ts +13 -1
  134. package/dist/fields/builtin/index.js +20 -7
  135. package/dist/fields/builtin/index.js.map +1 -1
  136. package/dist/fields/builtin/message.js +30 -0
  137. package/dist/fields/builtin/message.js.map +1 -0
  138. package/dist/fields/builtin/repeater.js +8 -3
  139. package/dist/fields/builtin/repeater.js.map +1 -1
  140. package/dist/fields/builtin/select.js +8 -3
  141. package/dist/fields/builtin/select.js.map +1 -1
  142. package/dist/fields/builtin/state.d.ts +13 -0
  143. package/dist/fields/builtin/state.js +30 -0
  144. package/dist/fields/builtin/state.js.map +1 -0
  145. package/dist/fields/data/regions.d.ts +21 -0
  146. package/dist/fields/data/regions.js +1238 -0
  147. package/dist/fields/data/regions.js.map +1 -0
  148. package/dist/fields/fieldKey.d.ts +15 -0
  149. package/dist/fields/fieldKey.js +16 -0
  150. package/dist/fields/fieldKey.js.map +1 -0
  151. package/dist/fields/fieldNamesOfType.js +13 -8
  152. package/dist/fields/fieldNamesOfType.js.map +1 -1
  153. package/dist/fields/instanceOptions.js +20 -0
  154. package/dist/fields/instanceOptions.js.map +1 -0
  155. package/dist/fields/localizedIf.d.ts +13 -0
  156. package/dist/fields/localizedIf.js +12 -0
  157. package/dist/fields/localizedIf.js.map +1 -0
  158. package/dist/fields/registry.d.ts +1 -0
  159. package/dist/fields/registry.js +21 -2
  160. package/dist/fields/registry.js.map +1 -1
  161. package/dist/fields/sharedConfig.js +101 -51
  162. package/dist/fields/sharedConfig.js.map +1 -1
  163. package/dist/fields/types.d.ts +61 -5
  164. package/dist/flow/engine.d.ts +6 -4
  165. package/dist/flow/engine.js +11 -6
  166. package/dist/flow/engine.js.map +1 -1
  167. package/dist/flow/normalizeFlow.js +22 -7
  168. package/dist/flow/normalizeFlow.js.map +1 -1
  169. package/dist/flow/stepLabel.js +15 -0
  170. package/dist/flow/stepLabel.js.map +1 -0
  171. package/dist/flow/types.d.ts +12 -3
  172. package/dist/flow/types.js +11 -0
  173. package/dist/flow/types.js.map +1 -0
  174. package/dist/form/pollState.d.ts +22 -0
  175. package/dist/form/pollState.js +17 -0
  176. package/dist/form/pollState.js.map +1 -0
  177. package/dist/form/toFormDocument.d.ts +85 -0
  178. package/dist/form/toFormDocument.js +75 -0
  179. package/dist/form/toFormDocument.js.map +1 -0
  180. package/dist/form/types.d.ts +65 -0
  181. package/dist/index.d.ts +193 -27
  182. package/dist/index.js +59 -19
  183. package/dist/index.js.map +1 -1
  184. package/dist/plugin/customState.js +26 -0
  185. package/dist/plugin/customState.js.map +1 -0
  186. package/dist/plugin/registerCollections.js +33 -44
  187. package/dist/plugin/registerCollections.js.map +1 -1
  188. package/dist/plugin/uploadsCollection.d.ts +13 -0
  189. package/dist/plugin/uploadsCollection.js +44 -0
  190. package/dist/plugin/uploadsCollection.js.map +1 -0
  191. package/dist/poll/applyPollOptions.js +18 -0
  192. package/dist/poll/applyPollOptions.js.map +1 -0
  193. package/dist/poll/buildPollOptionSourceFields.js +50 -0
  194. package/dist/poll/buildPollOptionSourceFields.js.map +1 -0
  195. package/dist/poll/closeJob.d.ts +52 -0
  196. package/dist/poll/closeJob.js +119 -0
  197. package/dist/poll/closeJob.js.map +1 -0
  198. package/dist/poll/definePollOptionSource.d.ts +44 -0
  199. package/dist/poll/definePollOptionSource.js +6 -0
  200. package/dist/poll/definePollOptionSource.js.map +1 -0
  201. package/dist/poll/definePollType.d.ts +34 -0
  202. package/dist/poll/definePollType.js +6 -0
  203. package/dist/poll/definePollType.js.map +1 -0
  204. package/dist/poll/effectivePollOptions.d.ts +38 -0
  205. package/dist/poll/effectivePollOptions.js +64 -0
  206. package/dist/poll/effectivePollOptions.js.map +1 -0
  207. package/dist/poll/mostVoted.d.ts +21 -0
  208. package/dist/poll/mostVoted.js +32 -0
  209. package/dist/poll/mostVoted.js.map +1 -0
  210. package/dist/poll/outcomeBeforeChange.js +91 -0
  211. package/dist/poll/outcomeBeforeChange.js.map +1 -0
  212. package/dist/poll/outcomeFields.d.ts +38 -0
  213. package/dist/poll/outcomeFields.js +52 -0
  214. package/dist/poll/outcomeFields.js.map +1 -0
  215. package/dist/poll/pollTypeRegistry.d.ts +34 -0
  216. package/dist/poll/pollTypeRegistry.js +71 -0
  217. package/dist/poll/pollTypeRegistry.js.map +1 -0
  218. package/dist/poll/registry.d.ts +16 -0
  219. package/dist/poll/registry.js +16 -0
  220. package/dist/poll/registry.js.map +1 -0
  221. package/dist/poll/resolvePollCloseRequest.js +105 -0
  222. package/dist/poll/resolvePollCloseRequest.js.map +1 -0
  223. package/dist/poll/resolvePollOptions.d.ts +29 -0
  224. package/dist/poll/resolvePollOptions.js +52 -0
  225. package/dist/poll/resolvePollOptions.js.map +1 -0
  226. package/dist/poll/resolvePollOptionsRequest.js +51 -0
  227. package/dist/poll/resolvePollOptionsRequest.js.map +1 -0
  228. package/dist/poll/resolvePollOutcome.d.ts +34 -0
  229. package/dist/poll/resolvePollOutcome.js +76 -0
  230. package/dist/poll/resolvePollOutcome.js.map +1 -0
  231. package/dist/poll/resultsField.js +21 -0
  232. package/dist/poll/resultsField.js.map +1 -0
  233. package/dist/prefill/valuesFromSearchParams.js +3 -2
  234. package/dist/prefill/valuesFromSearchParams.js.map +1 -1
  235. package/dist/presentations/types.d.ts +3 -2
  236. package/dist/react/Form.d.ts +15 -33
  237. package/dist/react/Form.js +144 -119
  238. package/dist/react/Form.js.map +1 -1
  239. package/dist/react/FormContext.d.ts +37 -1
  240. package/dist/react/FormContext.js.map +1 -1
  241. package/dist/react/FormControls.d.ts +55 -0
  242. package/dist/react/FormControls.js +56 -0
  243. package/dist/react/FormControls.js.map +1 -0
  244. package/dist/react/FormResults.d.ts +6 -3
  245. package/dist/react/FormResults.js +37 -29
  246. package/dist/react/FormResults.js.map +1 -1
  247. package/dist/react/FormSteps.d.ts +17 -0
  248. package/dist/react/FormSteps.js +35 -0
  249. package/dist/react/FormSteps.js.map +1 -0
  250. package/dist/react/Poll.d.ts +17 -6
  251. package/dist/react/Poll.js +66 -18
  252. package/dist/react/Poll.js.map +1 -1
  253. package/dist/react/contract.d.ts +2 -3
  254. package/dist/react/contract.js.map +1 -1
  255. package/dist/react/fetchResults.d.ts +1 -1
  256. package/dist/react/fetchResults.js +1 -1
  257. package/dist/react/fetchResults.js.map +1 -1
  258. package/dist/react/primitives/Checkbox.d.ts +4 -2
  259. package/dist/react/primitives/Checkbox.js +2 -1
  260. package/dist/react/primitives/Checkbox.js.map +1 -1
  261. package/dist/react/primitives/FieldShell.d.ts +2 -4
  262. package/dist/react/primitives/FieldShell.js +12 -19
  263. package/dist/react/primitives/FieldShell.js.map +1 -1
  264. package/dist/react/renderers/calculation.js +1 -2
  265. package/dist/react/renderers/calculation.js.map +1 -1
  266. package/dist/react/renderers/checkbox.js +1 -2
  267. package/dist/react/renderers/checkbox.js.map +1 -1
  268. package/dist/react/renderers/consent.js +78 -34
  269. package/dist/react/renderers/consent.js.map +1 -1
  270. package/dist/react/renderers/date.js +1 -3
  271. package/dist/react/renderers/date.js.map +1 -1
  272. package/dist/react/renderers/email.js +1 -2
  273. package/dist/react/renderers/email.js.map +1 -1
  274. package/dist/react/renderers/file.js +6 -3
  275. package/dist/react/renderers/file.js.map +1 -1
  276. package/dist/react/renderers/index.js +5 -0
  277. package/dist/react/renderers/index.js.map +1 -1
  278. package/dist/react/renderers/message.js +35 -0
  279. package/dist/react/renderers/message.js.map +1 -0
  280. package/dist/react/renderers/number.js +1 -2
  281. package/dist/react/renderers/number.js.map +1 -1
  282. package/dist/react/renderers/region.js +45 -0
  283. package/dist/react/renderers/region.js.map +1 -0
  284. package/dist/react/renderers/repeater.js +7 -10
  285. package/dist/react/renderers/repeater.js.map +1 -1
  286. package/dist/react/renderers/select.js +1 -2
  287. package/dist/react/renderers/select.js.map +1 -1
  288. package/dist/react/renderers/text.js +1 -2
  289. package/dist/react/renderers/text.js.map +1 -1
  290. package/dist/react/renderers/textarea.js +1 -2
  291. package/dist/react/renderers/textarea.js.map +1 -1
  292. package/dist/react/state.d.ts +23 -2
  293. package/dist/react/state.js +16 -7
  294. package/dist/react/state.js.map +1 -1
  295. package/dist/react/uploadFile.d.ts +2 -2
  296. package/dist/react/uploadFile.js +1 -1
  297. package/dist/react/uploadFile.js.map +1 -1
  298. package/dist/react/useField.d.ts +0 -1
  299. package/dist/react/useField.js +0 -1
  300. package/dist/react/useField.js.map +1 -1
  301. package/dist/react/useFormState.d.ts +1 -1
  302. package/dist/react/useFormState.js +1 -1
  303. package/dist/react/useFormState.js.map +1 -1
  304. package/dist/react/validateField.js +3 -6
  305. package/dist/react/validateField.js.map +1 -1
  306. package/dist/recall/interpolate.d.ts +4 -3
  307. package/dist/recall/interpolate.js +5 -4
  308. package/dist/recall/interpolate.js.map +1 -1
  309. package/dist/recall/resolver.d.ts +1 -1
  310. package/dist/recall/resolver.js +14 -2
  311. package/dist/recall/resolver.js.map +1 -1
  312. package/dist/spam/captcha.d.ts +1 -1
  313. package/dist/spam/captcha.js +1 -1
  314. package/dist/spam/captcha.js.map +1 -1
  315. package/dist/submissions/SubmissionAnswers.js +6 -2
  316. package/dist/submissions/SubmissionAnswers.js.map +1 -1
  317. package/dist/submissions/SubmissionAnswersClient.js +91 -62
  318. package/dist/submissions/SubmissionAnswersClient.js.map +1 -1
  319. package/dist/submissions/createSubmission.d.ts +36 -0
  320. package/dist/submissions/createSubmission.js +26 -0
  321. package/dist/submissions/createSubmission.js.map +1 -0
  322. package/dist/submissions/runSubmission.d.ts +10 -0
  323. package/dist/submissions/runSubmission.js +92 -44
  324. package/dist/submissions/runSubmission.js.map +1 -1
  325. package/dist/submissions/types.d.ts +11 -4
  326. package/dist/submissions/validateSubmission.js +59 -10
  327. package/dist/submissions/validateSubmission.js.map +1 -1
  328. package/dist/submissions/votedCookie.d.ts +13 -0
  329. package/dist/submissions/votedCookie.js +27 -0
  330. package/dist/submissions/votedCookie.js.map +1 -0
  331. package/dist/translations/de.js +295 -0
  332. package/dist/translations/de.js.map +1 -0
  333. package/dist/translations/en.js +138 -23
  334. package/dist/translations/en.js.map +1 -1
  335. package/dist/translations/index.d.ts +2 -1
  336. package/dist/translations/index.js +6 -2
  337. package/dist/translations/index.js.map +1 -1
  338. package/dist/translations/keys.d.ts +134 -19
  339. package/dist/translations/keys.js +135 -20
  340. package/dist/translations/keys.js.map +1 -1
  341. package/dist/uploads/types.d.ts +1 -2
  342. package/dist/validation/buildRuleBlocks.js +18 -17
  343. package/dist/validation/buildRuleBlocks.js.map +1 -1
  344. package/dist/validation/builtin/email.js +1 -0
  345. package/dist/validation/builtin/email.js.map +1 -1
  346. package/dist/validation/builtin/matchesField.js +3 -7
  347. package/dist/validation/builtin/matchesField.js.map +1 -1
  348. package/dist/validation/builtin/max.js +1 -0
  349. package/dist/validation/builtin/max.js.map +1 -1
  350. package/dist/validation/builtin/maxDate.js +1 -0
  351. package/dist/validation/builtin/maxDate.js.map +1 -1
  352. package/dist/validation/builtin/maxLength.js +1 -0
  353. package/dist/validation/builtin/maxLength.js.map +1 -1
  354. package/dist/validation/builtin/min.js +1 -0
  355. package/dist/validation/builtin/min.js.map +1 -1
  356. package/dist/validation/builtin/minDate.js +1 -0
  357. package/dist/validation/builtin/minDate.js.map +1 -1
  358. package/dist/validation/builtin/minLength.js +1 -0
  359. package/dist/validation/builtin/minLength.js.map +1 -1
  360. package/dist/validation/builtin/notAlreadySubmitted.js +1 -0
  361. package/dist/validation/builtin/notAlreadySubmitted.js.map +1 -1
  362. package/dist/validation/builtin/oneOf.js +1 -0
  363. package/dist/validation/builtin/oneOf.js.map +1 -1
  364. package/dist/validation/builtin/pattern.js +11 -2
  365. package/dist/validation/builtin/pattern.js.map +1 -1
  366. package/dist/validation/builtin/url.js +1 -0
  367. package/dist/validation/builtin/url.js.map +1 -1
  368. package/dist/validation/builtin/validateRegexParams.js +44 -0
  369. package/dist/validation/builtin/validateRegexParams.js.map +1 -0
  370. package/dist/validation/fieldTargetParam.d.ts +22 -0
  371. package/dist/validation/fieldTargetParam.js +38 -0
  372. package/dist/validation/fieldTargetParam.js.map +1 -0
  373. package/dist/validation/runValidation.js +8 -29
  374. package/dist/validation/runValidation.js.map +1 -1
  375. package/dist/validation/types.d.ts +5 -11
  376. package/dist/validation/validateUrl.js +22 -0
  377. package/dist/validation/validateUrl.js.map +1 -0
  378. package/package.json +7 -6
  379. package/styles/form-builder.css +10 -0
  380. package/dist/collections/uploads.d.ts +0 -30
  381. package/dist/collections/uploads.js +0 -55
  382. package/dist/collections/uploads.js.map +0 -1
  383. package/dist/consent/buildConsentSourceConfig.js +0 -49
  384. package/dist/consent/buildConsentSourceConfig.js.map +0 -1
  385. package/dist/consent/builtin/index.d.ts +0 -7
  386. package/dist/consent/builtin/index.js +0 -11
  387. package/dist/consent/builtin/index.js.map +0 -1
  388. package/dist/consent/builtin/pageReference.js +0 -75
  389. package/dist/consent/builtin/pageReference.js.map +0 -1
  390. package/dist/consent/builtin/static.js +0 -44
  391. package/dist/consent/builtin/static.js.map +0 -1
  392. package/dist/consent/defineConsentSource.d.ts +0 -35
  393. package/dist/consent/defineConsentSource.js +0 -6
  394. package/dist/consent/defineConsentSource.js.map +0 -1
  395. package/dist/consent/registry.d.ts +0 -16
  396. package/dist/consent/registry.js +0 -16
  397. package/dist/consent/registry.js.map +0 -1
  398. package/dist/consent/resolveConsentLinks.d.ts +0 -19
  399. package/dist/consent/resolveConsentLinks.js +0 -26
  400. package/dist/consent/resolveConsentLinks.js.map +0 -1
  401. package/dist/presentations/registry.d.ts +0 -15
  402. package/dist/presentations/registry.js +0 -15
  403. package/dist/presentations/registry.js.map +0 -1
  404. package/dist/react/toFormDocument.d.ts +0 -24
  405. package/dist/react/toFormDocument.js +0 -22
  406. package/dist/react/toFormDocument.js.map +0 -1
@@ -1,44 +1,92 @@
1
+ import { localizedIf } from "../../fields/localizedIf.js";
1
2
  import { interpolate } from "../../recall/interpolate.js";
2
3
  import { keys } from "../../translations/keys.js";
3
4
  import { labelFor } from "../../translations/server.js";
4
5
  import { resolverFor } from "../body/serializeBody.js";
5
6
  import { defineAction } from "../defineAction.js";
7
+ import { buildFromField } from "../fromAddresses.js";
8
+ import { buildToField } from "../../email/departments.js";
6
9
  //#region src/actions/builtin/emailTeam.ts
7
- const emailTeam = defineAction({
8
- type: "emailTeam",
9
- label: keys.actionEmailTeam,
10
- config: [
11
- {
12
- name: "to",
13
- type: "text",
14
- label: labelFor(keys.actionConfigTo)
15
- },
16
- {
17
- name: "subject",
18
- type: "text",
19
- label: labelFor(keys.actionConfigSubject)
20
- },
21
- {
22
- name: "body",
23
- type: "richText",
24
- label: labelFor(keys.actionConfigBody),
25
- admin: { description: labelFor(keys.actionConfigBodyDescription) }
10
+ /**
11
+ * `subject` and `body` are email content and follow `localize`; `from` is an address that never
12
+ * does. `to` is localized (following `localize`): a submission's locale selects the stored `to` at
13
+ * send, so a German submission routes to the German address. `cc`, `bcc`, and `replyTo` follow the
14
+ * same localized-address posture as `to`, each interpolated the same as `subject` so `{{field}}`
15
+ * merge tags work; a comma-separated list is forwarded as-is, since `payload.sendEmail`
16
+ * (nodemailer) accepts one directly. `editor` overrides the body field's Lexical/richText editor
17
+ * (from the plugin's `richText.editor` option). `fromAddresses`, when given (the plugin's
18
+ * `email.fromAddresses` option), adds a `from` select sourced from the host resolver; absent, no
19
+ * `from` field exists and every send uses the email adapter's default sender. `departments`, when
20
+ * given (the plugin's `email.departments` option), turns `to` into a select whose options are the
21
+ * host's resolved departments; absent, `to` stays a plain (localized) text field.
22
+ */
23
+ const buildEmailTeam = (localize, editor, fromAddresses, departments) => {
24
+ const toField = departments ? buildToField(departments, localize) : {
25
+ name: "to",
26
+ type: "text",
27
+ label: labelFor(keys.actionConfigTo),
28
+ ...localizedIf(localize)
29
+ };
30
+ return defineAction({
31
+ type: "emailTeam",
32
+ label: keys.actionEmailTeam,
33
+ config: [
34
+ toField,
35
+ ...fromAddresses ? [buildFromField(fromAddresses)] : [],
36
+ {
37
+ name: "cc",
38
+ type: "text",
39
+ label: labelFor(keys.actionConfigCc),
40
+ ...localizedIf(localize)
41
+ },
42
+ {
43
+ name: "bcc",
44
+ type: "text",
45
+ label: labelFor(keys.actionConfigBcc),
46
+ ...localizedIf(localize)
47
+ },
48
+ {
49
+ name: "replyTo",
50
+ type: "text",
51
+ label: labelFor(keys.actionConfigReplyTo),
52
+ ...localizedIf(localize)
53
+ },
54
+ {
55
+ name: "subject",
56
+ type: "text",
57
+ label: labelFor(keys.actionConfigSubject),
58
+ ...localizedIf(localize)
59
+ },
60
+ {
61
+ name: "body",
62
+ type: "richText",
63
+ label: labelFor(keys.actionConfigBody),
64
+ admin: { description: labelFor(keys.actionConfigBodyDescription) },
65
+ ...localizedIf(localize),
66
+ ...editor ? { editor } : {}
67
+ }
68
+ ],
69
+ run: async (args) => {
70
+ const { config, values, payload, renderBody } = args;
71
+ if (!config.to) throw new Error("emailTeam: missing \"to\" address");
72
+ if (typeof payload.sendEmail !== "function") throw new Error("emailTeam: no email adapter configured");
73
+ const resolve = resolverFor(values);
74
+ const subject = interpolate(config.subject ?? "", resolve);
75
+ const html = await renderBody(config.body);
76
+ await payload.sendEmail({
77
+ to: config.to,
78
+ subject,
79
+ html,
80
+ ...config.from ? { from: config.from } : {},
81
+ ...config.cc ? { cc: interpolate(config.cc, resolve) } : {},
82
+ ...config.bcc ? { bcc: interpolate(config.bcc, resolve) } : {},
83
+ ...config.replyTo ? { replyTo: interpolate(config.replyTo, resolve) } : {}
84
+ });
26
85
  }
27
- ],
28
- run: async (args) => {
29
- const { config, values, payload, renderBody } = args;
30
- if (!config.to) throw new Error("emailTeam: missing \"to\" address");
31
- if (typeof payload.sendEmail !== "function") throw new Error("emailTeam: no email adapter configured");
32
- const subject = interpolate(config.subject ?? "", resolverFor(values));
33
- const html = await renderBody(config.body);
34
- await payload.sendEmail({
35
- to: config.to,
36
- subject,
37
- html
38
- });
39
- }
40
- });
86
+ });
87
+ };
88
+ buildEmailTeam(true);
41
89
  //#endregion
42
- export { emailTeam };
90
+ export { buildEmailTeam };
43
91
 
44
92
  //# sourceMappingURL=emailTeam.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"emailTeam.js","names":[],"sources":["../../../src/actions/builtin/emailTeam.ts"],"sourcesContent":["import { interpolate } from '../../recall/interpolate'\nimport { keys } from '../../translations/keys'\nimport { labelFor } from '../../translations/server'\nimport { resolverFor } from '../body/serializeBody'\nimport { defineAction } from '../defineAction'\n\ntype EmailTeamConfig = { to?: string; subject?: string; body?: unknown }\n\nexport const emailTeam = defineAction<EmailTeamConfig>({\n\ttype: 'emailTeam',\n\tlabel: keys.actionEmailTeam,\n\tconfig: [\n\t\t{ name: 'to', type: 'text', label: labelFor(keys.actionConfigTo) },\n\t\t{ name: 'subject', type: 'text', label: labelFor(keys.actionConfigSubject) },\n\t\t{\n\t\t\tname: 'body',\n\t\t\ttype: 'richText',\n\t\t\tlabel: labelFor(keys.actionConfigBody),\n\t\t\tadmin: { description: labelFor(keys.actionConfigBodyDescription) },\n\t\t},\n\t],\n\trun: async (args) => {\n\t\tconst { config, values, payload, renderBody } = args\n\n\t\tif (!config.to) {\n\t\t\tthrow new Error('emailTeam: missing \"to\" address')\n\t\t}\n\n\t\tif (typeof payload.sendEmail !== 'function') {\n\t\t\tthrow new Error('emailTeam: no email adapter configured')\n\t\t}\n\n\t\tconst subject = interpolate(config.subject ?? '', resolverFor(values))\n\t\tconst html = await renderBody(config.body)\n\n\t\tawait payload.sendEmail({ to: config.to, subject, html })\n\t},\n})\n"],"mappings":";;;;;;AAQA,MAAa,YAAY,aAA8B;CACtD,MAAM;CACN,OAAO,KAAK;CACZ,QAAQ;EACP;GAAE,MAAM;GAAM,MAAM;GAAQ,OAAO,SAAS,KAAK,cAAc;EAAE;EACjE;GAAE,MAAM;GAAW,MAAM;GAAQ,OAAO,SAAS,KAAK,mBAAmB;EAAE;EAC3E;GACC,MAAM;GACN,MAAM;GACN,OAAO,SAAS,KAAK,gBAAgB;GACrC,OAAO,EAAE,aAAa,SAAS,KAAK,2BAA2B,EAAE;EAClE;CACD;CACA,KAAK,OAAO,SAAS;EACpB,MAAM,EAAE,QAAQ,QAAQ,SAAS,eAAe;EAEhD,IAAI,CAAC,OAAO,IACX,MAAM,IAAI,MAAM,mCAAiC;EAGlD,IAAI,OAAO,QAAQ,cAAc,YAChC,MAAM,IAAI,MAAM,wCAAwC;EAGzD,MAAM,UAAU,YAAY,OAAO,WAAW,IAAI,YAAY,MAAM,CAAC;EACrE,MAAM,OAAO,MAAM,WAAW,OAAO,IAAI;EAEzC,MAAM,QAAQ,UAAU;GAAE,IAAI,OAAO;GAAI;GAAS;EAAK,CAAC;CACzD;AACD,CAAC"}
1
+ {"version":3,"file":"emailTeam.js","names":[],"sources":["../../../src/actions/builtin/emailTeam.ts"],"sourcesContent":["import type { RichTextField, TextField } from 'payload'\nimport type { DepartmentEmailsResolver } from '../../email/departments'\nimport { buildToField } from '../../email/departments'\nimport { localizedIf } from '../../fields/localizedIf'\nimport { interpolate } from '../../recall/interpolate'\nimport { keys } from '../../translations/keys'\nimport { labelFor } from '../../translations/server'\nimport { resolverFor } from '../body/serializeBody'\nimport { defineAction } from '../defineAction'\nimport { buildFromField, type FromAddressesResolver } from '../fromAddresses'\n\ntype EmailTeamConfig = {\n\tto?: string\n\tfrom?: string\n\tcc?: string\n\tbcc?: string\n\treplyTo?: string\n\tsubject?: string\n\tbody?: unknown\n}\n\n/**\n * `subject` and `body` are email content and follow `localize`; `from` is an address that never\n * does. `to` is localized (following `localize`): a submission's locale selects the stored `to` at\n * send, so a German submission routes to the German address. `cc`, `bcc`, and `replyTo` follow the\n * same localized-address posture as `to`, each interpolated the same as `subject` so `{{field}}`\n * merge tags work; a comma-separated list is forwarded as-is, since `payload.sendEmail`\n * (nodemailer) accepts one directly. `editor` overrides the body field's Lexical/richText editor\n * (from the plugin's `richText.editor` option). `fromAddresses`, when given (the plugin's\n * `email.fromAddresses` option), adds a `from` select sourced from the host resolver; absent, no\n * `from` field exists and every send uses the email adapter's default sender. `departments`, when\n * given (the plugin's `email.departments` option), turns `to` into a select whose options are the\n * host's resolved departments; absent, `to` stays a plain (localized) text field.\n */\n// biome-ignore lint/complexity/useMaxParams: positional args mirror the fromAddresses threading (localize, editor, fromAddresses, departments)\nexport const buildEmailTeam = (\n\tlocalize: boolean,\n\teditor?: RichTextField['editor'],\n\tfromAddresses?: FromAddressesResolver,\n\tdepartments?: DepartmentEmailsResolver\n) => {\n\tconst toField: TextField = departments\n\t\t? buildToField(departments, localize)\n\t\t: { name: 'to', type: 'text', label: labelFor(keys.actionConfigTo), ...localizedIf(localize) }\n\treturn defineAction<EmailTeamConfig>({\n\t\ttype: 'emailTeam',\n\t\tlabel: keys.actionEmailTeam,\n\t\tconfig: [\n\t\t\ttoField,\n\t\t\t...(fromAddresses ? [buildFromField(fromAddresses)] : []),\n\t\t\t{\n\t\t\t\tname: 'cc',\n\t\t\t\ttype: 'text',\n\t\t\t\tlabel: labelFor(keys.actionConfigCc),\n\t\t\t\t...localizedIf(localize),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'bcc',\n\t\t\t\ttype: 'text',\n\t\t\t\tlabel: labelFor(keys.actionConfigBcc),\n\t\t\t\t...localizedIf(localize),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'replyTo',\n\t\t\t\ttype: 'text',\n\t\t\t\tlabel: labelFor(keys.actionConfigReplyTo),\n\t\t\t\t...localizedIf(localize),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'subject',\n\t\t\t\ttype: 'text',\n\t\t\t\tlabel: labelFor(keys.actionConfigSubject),\n\t\t\t\t...localizedIf(localize),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'body',\n\t\t\t\ttype: 'richText',\n\t\t\t\tlabel: labelFor(keys.actionConfigBody),\n\t\t\t\tadmin: { description: labelFor(keys.actionConfigBodyDescription) },\n\t\t\t\t...localizedIf(localize),\n\t\t\t\t...(editor ? { editor } : {}),\n\t\t\t},\n\t\t],\n\t\trun: async (args) => {\n\t\t\tconst { config, values, payload, renderBody } = args\n\n\t\t\tif (!config.to) {\n\t\t\t\tthrow new Error('emailTeam: missing \"to\" address')\n\t\t\t}\n\n\t\t\tif (typeof payload.sendEmail !== 'function') {\n\t\t\t\tthrow new Error('emailTeam: no email adapter configured')\n\t\t\t}\n\n\t\t\tconst resolve = resolverFor(values)\n\t\t\tconst subject = interpolate(config.subject ?? '', resolve)\n\t\t\tconst html = await renderBody(config.body)\n\n\t\t\t// `from` was validated at save time against `fromAddresses(req)`; not re-checked here\n\t\t\t// (the job's `req` may differ from the authoring admin's, and the config is\n\t\t\t// admin-authored, not visitor-controlled), so the stored value is forwarded verbatim.\n\t\t\tawait payload.sendEmail({\n\t\t\t\tto: config.to,\n\t\t\t\tsubject,\n\t\t\t\thtml,\n\t\t\t\t...(config.from ? { from: config.from } : {}),\n\t\t\t\t...(config.cc ? { cc: interpolate(config.cc, resolve) } : {}),\n\t\t\t\t...(config.bcc ? { bcc: interpolate(config.bcc, resolve) } : {}),\n\t\t\t\t...(config.replyTo ? { replyTo: interpolate(config.replyTo, resolve) } : {}),\n\t\t\t})\n\t\t},\n\t})\n}\n\nexport const emailTeam = buildEmailTeam(true)\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAmCA,MAAa,kBACZ,UACA,QACA,eACA,gBACI;CACJ,MAAM,UAAqB,cACxB,aAAa,aAAa,QAAQ,IAClC;EAAE,MAAM;EAAM,MAAM;EAAQ,OAAO,SAAS,KAAK,cAAc;EAAG,GAAG,YAAY,QAAQ;CAAE;CAC9F,OAAO,aAA8B;EACpC,MAAM;EACN,OAAO,KAAK;EACZ,QAAQ;GACP;GACA,GAAI,gBAAgB,CAAC,eAAe,aAAa,CAAC,IAAI,CAAC;GACvD;IACC,MAAM;IACN,MAAM;IACN,OAAO,SAAS,KAAK,cAAc;IACnC,GAAG,YAAY,QAAQ;GACxB;GACA;IACC,MAAM;IACN,MAAM;IACN,OAAO,SAAS,KAAK,eAAe;IACpC,GAAG,YAAY,QAAQ;GACxB;GACA;IACC,MAAM;IACN,MAAM;IACN,OAAO,SAAS,KAAK,mBAAmB;IACxC,GAAG,YAAY,QAAQ;GACxB;GACA;IACC,MAAM;IACN,MAAM;IACN,OAAO,SAAS,KAAK,mBAAmB;IACxC,GAAG,YAAY,QAAQ;GACxB;GACA;IACC,MAAM;IACN,MAAM;IACN,OAAO,SAAS,KAAK,gBAAgB;IACrC,OAAO,EAAE,aAAa,SAAS,KAAK,2BAA2B,EAAE;IACjE,GAAG,YAAY,QAAQ;IACvB,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;GAC5B;EACD;EACA,KAAK,OAAO,SAAS;GACpB,MAAM,EAAE,QAAQ,QAAQ,SAAS,eAAe;GAEhD,IAAI,CAAC,OAAO,IACX,MAAM,IAAI,MAAM,mCAAiC;GAGlD,IAAI,OAAO,QAAQ,cAAc,YAChC,MAAM,IAAI,MAAM,wCAAwC;GAGzD,MAAM,UAAU,YAAY,MAAM;GAClC,MAAM,UAAU,YAAY,OAAO,WAAW,IAAI,OAAO;GACzD,MAAM,OAAO,MAAM,WAAW,OAAO,IAAI;GAKzC,MAAM,QAAQ,UAAU;IACvB,IAAI,OAAO;IACX;IACA;IACA,GAAI,OAAO,OAAO,EAAE,MAAM,OAAO,KAAK,IAAI,CAAC;IAC3C,GAAI,OAAO,KAAK,EAAE,IAAI,YAAY,OAAO,IAAI,OAAO,EAAE,IAAI,CAAC;IAC3D,GAAI,OAAO,MAAM,EAAE,KAAK,YAAY,OAAO,KAAK,OAAO,EAAE,IAAI,CAAC;IAC9D,GAAI,OAAO,UAAU,EAAE,SAAS,YAAY,OAAO,SAAS,OAAO,EAAE,IAAI,CAAC;GAC3E,CAAC;EACF;CACD,CAAC;AACF;AAEyB,eAAe,IAAI"}
@@ -1,7 +1,24 @@
1
1
  import { AnyActionDefinition } from "../defineAction.js";
2
+ import { FromAddressesResolver } from "../fromAddresses.js";
3
+ import { DepartmentEmailsResolver } from "../../email/departments.js";
4
+ import { RichTextField } from "payload";
2
5
 
3
6
  //#region src/actions/builtin/index.d.ts
7
+ /**
8
+ * Built-in action definitions with content-bearing config fields (email subjects and rich text
9
+ * bodies) carrying `localized: true` when `localize` is true. `editor`, when given, overrides
10
+ * the Lexical/richText editor on both action bodies (the plugin passes
11
+ * `richText.bodyEditor ?? richText.editor`). `fromAddresses`, when given (the plugin's
12
+ * `email.fromAddresses` option), adds a `from` select to both email actions. `departments`, when
13
+ * given (the plugin's `email.departments` option), turns the `emailTeam` `to` into a select over the
14
+ * host's resolved departments. `signedWebhook` has no content fields and is shared static.
15
+ */
16
+ declare const buildDefaultActionDefinitions: (localize: boolean, editor?: RichTextField["editor"], fromAddresses?: FromAddressesResolver, departments?: DepartmentEmailsResolver) => Record<string, AnyActionDefinition>;
17
+ /**
18
+ * Prebuilt with content localization on; with `localizeContent: false`, derive overrides from
19
+ * `buildDefaultActionDefinitions(false)` instead so a spread does not reintroduce `localized`.
20
+ */
4
21
  declare const defaultActionDefinitions: Record<string, AnyActionDefinition>;
5
22
  //#endregion
6
- export { defaultActionDefinitions };
23
+ export { buildDefaultActionDefinitions, defaultActionDefinitions };
7
24
  //# sourceMappingURL=index.d.ts.map
@@ -1,13 +1,27 @@
1
- import { confirmation } from "./confirmation.js";
2
- import { emailTeam } from "./emailTeam.js";
1
+ import { buildConfirmation } from "./confirmation.js";
2
+ import { buildEmailTeam } from "./emailTeam.js";
3
3
  import { signedWebhook } from "./signedWebhook.js";
4
4
  //#region src/actions/builtin/index.ts
5
- const defaultActionDefinitions = {
6
- emailTeam,
7
- confirmation,
5
+ /**
6
+ * Built-in action definitions with content-bearing config fields (email subjects and rich text
7
+ * bodies) carrying `localized: true` when `localize` is true. `editor`, when given, overrides
8
+ * the Lexical/richText editor on both action bodies (the plugin passes
9
+ * `richText.bodyEditor ?? richText.editor`). `fromAddresses`, when given (the plugin's
10
+ * `email.fromAddresses` option), adds a `from` select to both email actions. `departments`, when
11
+ * given (the plugin's `email.departments` option), turns the `emailTeam` `to` into a select over the
12
+ * host's resolved departments. `signedWebhook` has no content fields and is shared static.
13
+ */
14
+ const buildDefaultActionDefinitions = (localize, editor, fromAddresses, departments) => ({
15
+ emailTeam: buildEmailTeam(localize, editor, fromAddresses, departments),
16
+ confirmation: buildConfirmation(localize, editor, fromAddresses),
8
17
  signedWebhook
9
- };
18
+ });
19
+ /**
20
+ * Prebuilt with content localization on; with `localizeContent: false`, derive overrides from
21
+ * `buildDefaultActionDefinitions(false)` instead so a spread does not reintroduce `localized`.
22
+ */
23
+ const defaultActionDefinitions = buildDefaultActionDefinitions(true);
10
24
  //#endregion
11
- export { defaultActionDefinitions };
25
+ export { buildDefaultActionDefinitions, defaultActionDefinitions };
12
26
 
13
27
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/actions/builtin/index.ts"],"sourcesContent":["import type { AnyActionDefinition } from '../defineAction'\nimport { confirmation } from './confirmation'\nimport { emailTeam } from './emailTeam'\nimport { signedWebhook } from './signedWebhook'\n\nexport const defaultActionDefinitions: Record<string, AnyActionDefinition> = {\n\temailTeam: emailTeam as AnyActionDefinition,\n\tconfirmation: confirmation as AnyActionDefinition,\n\tsignedWebhook: signedWebhook as AnyActionDefinition,\n}\n"],"mappings":";;;;AAKA,MAAa,2BAAgE;CACjE;CACG;CACC;AAChB"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/actions/builtin/index.ts"],"sourcesContent":["import type { RichTextField } from 'payload'\nimport type { DepartmentEmailsResolver } from '../../email/departments'\nimport type { AnyActionDefinition } from '../defineAction'\nimport type { FromAddressesResolver } from '../fromAddresses'\nimport { buildConfirmation } from './confirmation'\nimport { buildEmailTeam } from './emailTeam'\nimport { signedWebhook } from './signedWebhook'\n\n/**\n * Built-in action definitions with content-bearing config fields (email subjects and rich text\n * bodies) carrying `localized: true` when `localize` is true. `editor`, when given, overrides\n * the Lexical/richText editor on both action bodies (the plugin passes\n * `richText.bodyEditor ?? richText.editor`). `fromAddresses`, when given (the plugin's\n * `email.fromAddresses` option), adds a `from` select to both email actions. `departments`, when\n * given (the plugin's `email.departments` option), turns the `emailTeam` `to` into a select over the\n * host's resolved departments. `signedWebhook` has no content fields and is shared static.\n */\n// biome-ignore lint/complexity/useMaxParams: positional args thread the plugin editor and email resolvers (localize, editor, fromAddresses, departments)\nexport const buildDefaultActionDefinitions = (\n\tlocalize: boolean,\n\teditor?: RichTextField['editor'],\n\tfromAddresses?: FromAddressesResolver,\n\tdepartments?: DepartmentEmailsResolver\n): Record<string, AnyActionDefinition> => ({\n\temailTeam: buildEmailTeam(localize, editor, fromAddresses, departments) as AnyActionDefinition,\n\tconfirmation: buildConfirmation(localize, editor, fromAddresses) as AnyActionDefinition,\n\tsignedWebhook: signedWebhook as AnyActionDefinition,\n})\n\n/**\n * Prebuilt with content localization on; with `localizeContent: false`, derive overrides from\n * `buildDefaultActionDefinitions(false)` instead so a spread does not reintroduce `localized`.\n */\nexport const defaultActionDefinitions: Record<string, AnyActionDefinition> =\n\tbuildDefaultActionDefinitions(true)\n"],"mappings":";;;;;;;;;;;;;AAkBA,MAAa,iCACZ,UACA,QACA,eACA,iBAC0C;CAC1C,WAAW,eAAe,UAAU,QAAQ,eAAe,WAAW;CACtE,cAAc,kBAAkB,UAAU,QAAQ,aAAa;CAChD;AAChB;;;;;AAMA,MAAa,2BACZ,8BAA8B,IAAI"}
@@ -1,23 +1,9 @@
1
1
  import { keys } from "../../translations/keys.js";
2
- import { asTranslate, labelFor } from "../../translations/server.js";
2
+ import { labelFor } from "../../translations/server.js";
3
3
  import { defineAction } from "../defineAction.js";
4
+ import { validateUrl } from "../../validation/validateUrl.js";
4
5
  import { SIGNATURE_HEADER, signPayload } from "../sign.js";
5
6
  //#region src/actions/builtin/signedWebhook.ts
6
- /**
7
- * Validates the signed-webhook action's `url`: unset is fine (the runtime throws on a missing
8
- * URL when the action actually runs), otherwise it must parse as an absolute http(s) URL.
9
- * Exported for unit testing.
10
- */
11
- const validateUrl = (value, { req }) => {
12
- if (typeof value !== "string" || value.length === 0) return true;
13
- let parsed;
14
- try {
15
- parsed = new URL(value);
16
- } catch {
17
- return asTranslate(req.t)(keys.validationUrlInvalid);
18
- }
19
- return parsed.protocol === "http:" || parsed.protocol === "https:" ? true : asTranslate(req.t)(keys.validationUrlInvalid);
20
- };
21
7
  const signedWebhook = defineAction({
22
8
  type: "signedWebhook",
23
9
  label: keys.actionSignedWebhook,
@@ -1 +1 @@
1
- {"version":3,"file":"signedWebhook.js","names":[],"sources":["../../../src/actions/builtin/signedWebhook.ts"],"sourcesContent":["import type { PayloadRequest } from 'payload'\nimport { keys } from '../../translations/keys'\nimport { asTranslate, labelFor } from '../../translations/server'\nimport { defineAction } from '../defineAction'\nimport { SIGNATURE_HEADER, signPayload } from '../sign'\n\ntype SignedWebhookConfig = { url?: string; secret?: string }\n\n/**\n * Validates the signed-webhook action's `url`: unset is fine (the runtime throws on a missing\n * URL when the action actually runs), otherwise it must parse as an absolute http(s) URL.\n * Exported for unit testing.\n */\nexport const validateUrl = (\n\tvalue: unknown,\n\t{ req }: { data?: unknown; req: PayloadRequest }\n): string | true => {\n\tif (typeof value !== 'string' || value.length === 0) {\n\t\treturn true\n\t}\n\tlet parsed: URL\n\ttry {\n\t\tparsed = new URL(value)\n\t} catch {\n\t\treturn asTranslate(req.t)(keys.validationUrlInvalid)\n\t}\n\treturn parsed.protocol === 'http:' || parsed.protocol === 'https:'\n\t\t? true\n\t\t: asTranslate(req.t)(keys.validationUrlInvalid)\n}\n\nexport const signedWebhook = defineAction<SignedWebhookConfig>({\n\ttype: 'signedWebhook',\n\tlabel: keys.actionSignedWebhook,\n\tconfig: [\n\t\t{\n\t\t\tname: 'url',\n\t\t\ttype: 'text',\n\t\t\tlabel: labelFor(keys.actionConfigUrl),\n\t\t\tadmin: { description: labelFor(keys.actionConfigUrlDescription) },\n\t\t\tvalidate: validateUrl,\n\t\t},\n\t\t{\n\t\t\tname: 'secret',\n\t\t\ttype: 'text',\n\t\t\tlabel: labelFor(keys.actionConfigSecret),\n\t\t\tadmin: { description: labelFor(keys.actionConfigSecretDescription) },\n\t\t},\n\t],\n\trun: async (args) => {\n\t\tconst { config, form, submissionId, values } = args\n\n\t\tif (!config.url) {\n\t\t\tthrow new Error('signedWebhook: missing \"url\"')\n\t\t}\n\t\tif (!config.secret) {\n\t\t\tthrow new Error('signedWebhook: missing \"secret\"')\n\t\t}\n\n\t\tconst body = JSON.stringify({ formId: form.id, submissionId, values })\n\t\tconst controller = new AbortController()\n\t\tconst timer = setTimeout(() => controller.abort(), 10_000)\n\n\t\tlet response: Response\n\t\ttry {\n\t\t\tresponse = await fetch(config.url, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: {\n\t\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\t\t[SIGNATURE_HEADER]: signPayload(body, config.secret),\n\t\t\t\t},\n\t\t\t\tbody,\n\t\t\t\tsignal: controller.signal,\n\t\t\t})\n\t\t} finally {\n\t\t\tclearTimeout(timer)\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(`signedWebhook: server responded ${response.status}`)\n\t\t}\n\t},\n})\n"],"mappings":";;;;;;;;;;AAaA,MAAa,eACZ,OACA,EAAE,UACiB;CACnB,IAAI,OAAO,UAAU,YAAY,MAAM,WAAW,GACjD,OAAO;CAER,IAAI;CACJ,IAAI;EACH,SAAS,IAAI,IAAI,KAAK;CACvB,QAAQ;EACP,OAAO,YAAY,IAAI,CAAC,EAAE,KAAK,oBAAoB;CACpD;CACA,OAAO,OAAO,aAAa,WAAW,OAAO,aAAa,WACvD,OACA,YAAY,IAAI,CAAC,EAAE,KAAK,oBAAoB;AAChD;AAEA,MAAa,gBAAgB,aAAkC;CAC9D,MAAM;CACN,OAAO,KAAK;CACZ,QAAQ,CACP;EACC,MAAM;EACN,MAAM;EACN,OAAO,SAAS,KAAK,eAAe;EACpC,OAAO,EAAE,aAAa,SAAS,KAAK,0BAA0B,EAAE;EAChE,UAAU;CACX,GACA;EACC,MAAM;EACN,MAAM;EACN,OAAO,SAAS,KAAK,kBAAkB;EACvC,OAAO,EAAE,aAAa,SAAS,KAAK,6BAA6B,EAAE;CACpE,CACD;CACA,KAAK,OAAO,SAAS;EACpB,MAAM,EAAE,QAAQ,MAAM,cAAc,WAAW;EAE/C,IAAI,CAAC,OAAO,KACX,MAAM,IAAI,MAAM,gCAA8B;EAE/C,IAAI,CAAC,OAAO,QACX,MAAM,IAAI,MAAM,mCAAiC;EAGlD,MAAM,OAAO,KAAK,UAAU;GAAE,QAAQ,KAAK;GAAI;GAAc;EAAO,CAAC;EACrE,MAAM,aAAa,IAAI,gBAAgB;EACvC,MAAM,QAAQ,iBAAiB,WAAW,MAAM,GAAG,GAAM;EAEzD,IAAI;EACJ,IAAI;GACH,WAAW,MAAM,MAAM,OAAO,KAAK;IAClC,QAAQ;IACR,SAAS;KACR,gBAAgB;MACf,mBAAmB,YAAY,MAAM,OAAO,MAAM;IACpD;IACA;IACA,QAAQ,WAAW;GACpB,CAAC;EACF,UAAU;GACT,aAAa,KAAK;EACnB;EAEA,IAAI,CAAC,SAAS,IACb,MAAM,IAAI,MAAM,mCAAmC,SAAS,QAAQ;CAEtE;AACD,CAAC"}
1
+ {"version":3,"file":"signedWebhook.js","names":[],"sources":["../../../src/actions/builtin/signedWebhook.ts"],"sourcesContent":["import { keys } from '../../translations/keys'\nimport { labelFor } from '../../translations/server'\nimport { validateUrl } from '../../validation/validateUrl'\nimport { defineAction } from '../defineAction'\nimport { SIGNATURE_HEADER, signPayload } from '../sign'\n\nexport { validateUrl } from '../../validation/validateUrl'\n\ntype SignedWebhookConfig = { url?: string; secret?: string }\n\nexport const signedWebhook = defineAction<SignedWebhookConfig>({\n\ttype: 'signedWebhook',\n\tlabel: keys.actionSignedWebhook,\n\tconfig: [\n\t\t{\n\t\t\tname: 'url',\n\t\t\ttype: 'text',\n\t\t\tlabel: labelFor(keys.actionConfigUrl),\n\t\t\tadmin: { description: labelFor(keys.actionConfigUrlDescription) },\n\t\t\tvalidate: validateUrl,\n\t\t},\n\t\t{\n\t\t\tname: 'secret',\n\t\t\ttype: 'text',\n\t\t\tlabel: labelFor(keys.actionConfigSecret),\n\t\t\tadmin: { description: labelFor(keys.actionConfigSecretDescription) },\n\t\t},\n\t],\n\trun: async (args) => {\n\t\tconst { config, form, submissionId, values } = args\n\n\t\tif (!config.url) {\n\t\t\tthrow new Error('signedWebhook: missing \"url\"')\n\t\t}\n\t\tif (!config.secret) {\n\t\t\tthrow new Error('signedWebhook: missing \"secret\"')\n\t\t}\n\n\t\tconst body = JSON.stringify({ formId: form.id, submissionId, values })\n\t\tconst controller = new AbortController()\n\t\tconst timer = setTimeout(() => controller.abort(), 10_000)\n\n\t\tlet response: Response\n\t\ttry {\n\t\t\tresponse = await fetch(config.url, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: {\n\t\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\t\t[SIGNATURE_HEADER]: signPayload(body, config.secret),\n\t\t\t\t},\n\t\t\t\tbody,\n\t\t\t\tsignal: controller.signal,\n\t\t\t})\n\t\t} finally {\n\t\t\tclearTimeout(timer)\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(`signedWebhook: server responded ${response.status}`)\n\t\t}\n\t},\n})\n"],"mappings":";;;;;;AAUA,MAAa,gBAAgB,aAAkC;CAC9D,MAAM;CACN,OAAO,KAAK;CACZ,QAAQ,CACP;EACC,MAAM;EACN,MAAM;EACN,OAAO,SAAS,KAAK,eAAe;EACpC,OAAO,EAAE,aAAa,SAAS,KAAK,0BAA0B,EAAE;EAChE,UAAU;CACX,GACA;EACC,MAAM;EACN,MAAM;EACN,OAAO,SAAS,KAAK,kBAAkB;EACvC,OAAO,EAAE,aAAa,SAAS,KAAK,6BAA6B,EAAE;CACpE,CACD;CACA,KAAK,OAAO,SAAS;EACpB,MAAM,EAAE,QAAQ,MAAM,cAAc,WAAW;EAE/C,IAAI,CAAC,OAAO,KACX,MAAM,IAAI,MAAM,gCAA8B;EAE/C,IAAI,CAAC,OAAO,QACX,MAAM,IAAI,MAAM,mCAAiC;EAGlD,MAAM,OAAO,KAAK,UAAU;GAAE,QAAQ,KAAK;GAAI;GAAc;EAAO,CAAC;EACrE,MAAM,aAAa,IAAI,gBAAgB;EACvC,MAAM,QAAQ,iBAAiB,WAAW,MAAM,GAAG,GAAM;EAEzD,IAAI;EACJ,IAAI;GACH,WAAW,MAAM,MAAM,OAAO,KAAK;IAClC,QAAQ;IACR,SAAS;KACR,gBAAgB;MACf,mBAAmB,YAAY,MAAM,OAAO,MAAM;IACpD;IACA;IACA,QAAQ,WAAW;GACpB,CAAC;EACF,UAAU;GACT,aAAa,KAAK;EACnB;EAEA,IAAI,CAAC,SAAS,IACb,MAAM,IAAI,MAAM,mCAAmC,SAAS,QAAQ;CAEtE;AACD,CAAC"}
@@ -1,5 +1,5 @@
1
- import { Translate } from "../fields/types.js";
2
1
  import { SubmissionDescriptor, SubmissionValue } from "../submissions/types.js";
2
+ import { Translate } from "../fields/types.js";
3
3
  import { Field, Payload, PayloadRequest } from "payload";
4
4
 
5
5
  //#region src/actions/defineAction.d.ts
@@ -0,0 +1,21 @@
1
+ import { PayloadRequest } from "payload";
2
+
3
+ //#region src/actions/fromAddresses.d.ts
4
+ /** One selectable "from" address for the built-in email actions. */
5
+ type FromAddressOption = {
6
+ label: string;
7
+ value: string;
8
+ };
9
+ /**
10
+ * Host seam resolving the selectable `from` addresses for `emailTeam`/`confirmation`
11
+ * (plugin option `email.fromAddresses`). Multi-tenant hosts derive tenant scoping from `req`
12
+ * (host header, cookie, or auth context) and return only that tenant's allowed senders. `value`
13
+ * is the literal string handed to `payload.sendEmail`'s `from` (e.g. `'Name <addr@x.com>'` or a
14
+ * plain address). Absent keeps the email adapter's default sender and adds no `from` field at all.
15
+ */
16
+ type FromAddressesResolver = (args: {
17
+ req: PayloadRequest;
18
+ }) => Promise<FromAddressOption[]> | FromAddressOption[];
19
+ //#endregion
20
+ export { FromAddressOption, FromAddressesResolver };
21
+ //# sourceMappingURL=fromAddresses.d.ts.map
@@ -0,0 +1,74 @@
1
+ import { keys } from "../translations/keys.js";
2
+ import { asTranslate, labelFor } from "../translations/server.js";
3
+ //#region src/actions/fromAddresses.ts
4
+ const FROM_FIELD_REF = "@10x-media/form-builder/client#EndpointOptionsSelect";
5
+ /**
6
+ * Validate for the `from` field, closed over the host resolver (mirrors the confirmation action's
7
+ * `toField` and poll's `resultsField`): unset is fine, otherwise the value must be one of the
8
+ * resolver's options for this request. A throwing resolver fails closed with a translated message
9
+ * rather than surfacing a raw error on save.
10
+ *
11
+ * Failing closed has an operational cost worth knowing: Payload runs this on every save, not only
12
+ * when `from` changed, so for as long as the resolver is down no form carrying an email action with
13
+ * a `from` set can be saved at all, including edits that never touch the address. That is the
14
+ * deliberate trade: failing open would persist a sender the host can no longer vouch for, and
15
+ * unlike `toField` and `resultsField` this seam depends on host infrastructure that can be down.
16
+ * A resolver reaching a flaky upstream should cache or fall back internally rather than throw.
17
+ */
18
+ const validateFromField = (resolver) => async (value, { req }) => {
19
+ if (typeof value !== "string" || value.length === 0) return true;
20
+ let options;
21
+ try {
22
+ options = await resolver({ req });
23
+ } catch {
24
+ return asTranslate(req.t)(keys.validationFromUnavailable);
25
+ }
26
+ return options.some((option) => option.value === value) ? true : asTranslate(req.t)(keys.validationFromUnknown);
27
+ };
28
+ /**
29
+ * The `from` select shared by `emailTeam` and `confirmation`: an `EndpointOptionsSelect` backed by
30
+ * the forms collection's `/:id/from-addresses` endpoint (registered only when `email.fromAddresses`
31
+ * is set). That route's document id goes unused server-side: the option set is request-scoped, not
32
+ * per-form, so this reuses the existing doc-scoped component as-is instead of adding an id-less mode.
33
+ */
34
+ const buildFromField = (resolver) => ({
35
+ name: "from",
36
+ type: "text",
37
+ label: labelFor(keys.actionConfigFrom),
38
+ validate: validateFromField(resolver),
39
+ admin: { components: { Field: {
40
+ path: FROM_FIELD_REF,
41
+ clientProps: {
42
+ endpoint: "from-addresses",
43
+ descriptionKey: keys.actionConfigFromDescription
44
+ }
45
+ } } }
46
+ });
47
+ /**
48
+ * Authorize and resolve the `GET /:id/from-addresses` request backing the `from` selects:
49
+ * authenticated callers get the host resolver's current options for this request; anonymous
50
+ * callers are always refused. The route id is unused (see `buildFromField`). Statuses mirror
51
+ * the poll-options endpoint: 403 unauthenticated, 503 when the resolver throws (fail closed).
52
+ */
53
+ const resolveFromAddressesRequest = async (args) => {
54
+ const { isAuthed, req, resolver } = args;
55
+ if (!isAuthed) return {
56
+ status: 403,
57
+ body: { errors: [{ message: "Forbidden" }] }
58
+ };
59
+ try {
60
+ return {
61
+ status: 200,
62
+ body: { options: await resolver({ req }) }
63
+ };
64
+ } catch {
65
+ return {
66
+ status: 503,
67
+ body: { errors: [{ message: "From addresses unavailable" }] }
68
+ };
69
+ }
70
+ };
71
+ //#endregion
72
+ export { buildFromField, resolveFromAddressesRequest };
73
+
74
+ //# sourceMappingURL=fromAddresses.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fromAddresses.js","names":[],"sources":["../../src/actions/fromAddresses.ts"],"sourcesContent":["import type { PayloadRequest, TextField } from 'payload'\nimport { keys } from '../translations/keys'\nimport { asTranslate, labelFor } from '../translations/server'\n\n/** One selectable \"from\" address for the built-in email actions. */\nexport type FromAddressOption = { label: string; value: string }\n\n/**\n * Host seam resolving the selectable `from` addresses for `emailTeam`/`confirmation`\n * (plugin option `email.fromAddresses`). Multi-tenant hosts derive tenant scoping from `req`\n * (host header, cookie, or auth context) and return only that tenant's allowed senders. `value`\n * is the literal string handed to `payload.sendEmail`'s `from` (e.g. `'Name <addr@x.com>'` or a\n * plain address). Absent keeps the email adapter's default sender and adds no `from` field at all.\n */\nexport type FromAddressesResolver = (args: {\n\treq: PayloadRequest\n}) => Promise<FromAddressOption[]> | FromAddressOption[]\n\nconst FROM_FIELD_REF = '@10x-media/form-builder/client#EndpointOptionsSelect'\n\n/**\n * Validate for the `from` field, closed over the host resolver (mirrors the confirmation action's\n * `toField` and poll's `resultsField`): unset is fine, otherwise the value must be one of the\n * resolver's options for this request. A throwing resolver fails closed with a translated message\n * rather than surfacing a raw error on save.\n *\n * Failing closed has an operational cost worth knowing: Payload runs this on every save, not only\n * when `from` changed, so for as long as the resolver is down no form carrying an email action with\n * a `from` set can be saved at all, including edits that never touch the address. That is the\n * deliberate trade: failing open would persist a sender the host can no longer vouch for, and\n * unlike `toField` and `resultsField` this seam depends on host infrastructure that can be down.\n * A resolver reaching a flaky upstream should cache or fall back internally rather than throw.\n */\nexport const validateFromField =\n\t(resolver: FromAddressesResolver) =>\n\tasync (value: unknown, { req }: { req: PayloadRequest }): Promise<string | true> => {\n\t\tif (typeof value !== 'string' || value.length === 0) {\n\t\t\treturn true\n\t\t}\n\t\tlet options: FromAddressOption[]\n\t\ttry {\n\t\t\toptions = await resolver({ req })\n\t\t} catch {\n\t\t\treturn asTranslate(req.t)(keys.validationFromUnavailable)\n\t\t}\n\t\treturn options.some((option) => option.value === value)\n\t\t\t? true\n\t\t\t: asTranslate(req.t)(keys.validationFromUnknown)\n\t}\n\n/**\n * The `from` select shared by `emailTeam` and `confirmation`: an `EndpointOptionsSelect` backed by\n * the forms collection's `/:id/from-addresses` endpoint (registered only when `email.fromAddresses`\n * is set). That route's document id goes unused server-side: the option set is request-scoped, not\n * per-form, so this reuses the existing doc-scoped component as-is instead of adding an id-less mode.\n */\nexport const buildFromField = (resolver: FromAddressesResolver): TextField => ({\n\tname: 'from',\n\ttype: 'text',\n\tlabel: labelFor(keys.actionConfigFrom),\n\tvalidate: validateFromField(resolver),\n\tadmin: {\n\t\tcomponents: {\n\t\t\tField: {\n\t\t\t\tpath: FROM_FIELD_REF,\n\t\t\t\tclientProps: {\n\t\t\t\t\tendpoint: 'from-addresses',\n\t\t\t\t\tdescriptionKey: keys.actionConfigFromDescription,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t},\n})\n\nexport type ResolveFromAddressesRequestArgs = {\n\t/** Whether the caller is authenticated (an admin/user). */\n\tisAuthed: boolean\n\treq: PayloadRequest\n\tresolver: FromAddressesResolver\n}\n\nexport type ResolveFromAddressesRequestResult = {\n\tstatus: number\n\tbody: { options: FromAddressOption[] } | { errors: { message: string }[] }\n}\n\n/**\n * Authorize and resolve the `GET /:id/from-addresses` request backing the `from` selects:\n * authenticated callers get the host resolver's current options for this request; anonymous\n * callers are always refused. The route id is unused (see `buildFromField`). Statuses mirror\n * the poll-options endpoint: 403 unauthenticated, 503 when the resolver throws (fail closed).\n */\nexport const resolveFromAddressesRequest = async (\n\targs: ResolveFromAddressesRequestArgs\n): Promise<ResolveFromAddressesRequestResult> => {\n\tconst { isAuthed, req, resolver } = args\n\tif (!isAuthed) {\n\t\treturn { status: 403, body: { errors: [{ message: 'Forbidden' }] } }\n\t}\n\ttry {\n\t\tconst options = await resolver({ req })\n\t\treturn { status: 200, body: { options } }\n\t} catch {\n\t\treturn { status: 503, body: { errors: [{ message: 'From addresses unavailable' }] } }\n\t}\n}\n"],"mappings":";;;AAkBA,MAAM,iBAAiB;;;;;;;;;;;;;;AAevB,MAAa,qBACX,aACD,OAAO,OAAgB,EAAE,UAA2D;CACnF,IAAI,OAAO,UAAU,YAAY,MAAM,WAAW,GACjD,OAAO;CAER,IAAI;CACJ,IAAI;EACH,UAAU,MAAM,SAAS,EAAE,IAAI,CAAC;CACjC,QAAQ;EACP,OAAO,YAAY,IAAI,CAAC,EAAE,KAAK,yBAAyB;CACzD;CACA,OAAO,QAAQ,MAAM,WAAW,OAAO,UAAU,KAAK,IACnD,OACA,YAAY,IAAI,CAAC,EAAE,KAAK,qBAAqB;AACjD;;;;;;;AAQD,MAAa,kBAAkB,cAAgD;CAC9E,MAAM;CACN,MAAM;CACN,OAAO,SAAS,KAAK,gBAAgB;CACrC,UAAU,kBAAkB,QAAQ;CACpC,OAAO,EACN,YAAY,EACX,OAAO;EACN,MAAM;EACN,aAAa;GACZ,UAAU;GACV,gBAAgB,KAAK;EACtB;CACD,EACD,EACD;AACD;;;;;;;AAoBA,MAAa,8BAA8B,OAC1C,SACgD;CAChD,MAAM,EAAE,UAAU,KAAK,aAAa;CACpC,IAAI,CAAC,UACJ,OAAO;EAAE,QAAQ;EAAK,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,YAAY,CAAC,EAAE;CAAE;CAEpE,IAAI;EAEH,OAAO;GAAE,QAAQ;GAAK,MAAM,EAAE,SAAA,MADR,SAAS,EAAE,IAAI,CAAC,EACA;EAAE;CACzC,QAAQ;EACP,OAAO;GAAE,QAAQ;GAAK,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,6BAA6B,CAAC,EAAE;EAAE;CACrF;AACD"}
@@ -9,6 +9,8 @@ const runActions = async (args) => {
9
9
  const renderBody = makeRenderBody({
10
10
  values: ctx.values,
11
11
  descriptors: ctx.descriptors,
12
+ form: ctx.form,
13
+ req: ctx.req,
12
14
  richText
13
15
  });
14
16
  const results = [];
@@ -1 +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({ values: ctx.values, descriptors: ctx.descriptors, richText })\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;EAAE,QAAQ,IAAI;EAAQ,aAAa,IAAI;EAAa;CAAS,CAAC;CAChG,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"}
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"}
@@ -14,14 +14,6 @@ const asDescriptors = (value) => Array.isArray(value) ? value : [];
14
14
  */
15
15
  const runActionsForSubmission = async (args) => {
16
16
  const { input, registry, payload, req, richText } = args;
17
- const form = await payload.findByID({
18
- collection: FORMS_SLUG,
19
- id: input.formId,
20
- depth: 0,
21
- overrideAccess: true,
22
- req
23
- }).catch(() => null);
24
- if (!form) return;
25
17
  const submission = await payload.findByID({
26
18
  collection: FORM_SUBMISSIONS_SLUG,
27
19
  id: input.submissionId,
@@ -31,6 +23,15 @@ const runActionsForSubmission = async (args) => {
31
23
  }).catch(() => null);
32
24
  if (!submission) return;
33
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;
34
35
  const t = asFieldTranslate(req?.i18n?.t ?? ((key) => key));
35
36
  await runActions({
36
37
  actions: asActions(form.actions),
@@ -1 +1 @@
1
- {"version":3,"file":"task.js","names":[],"sources":["../../src/actions/task.ts"],"sourcesContent":["import type { Config, Payload, PayloadRequest, TaskConfig } from 'payload'\nimport { FORM_SUBMISSIONS_SLUG } from '../collections/formSubmissions'\nimport { FORMS_SLUG } from '../collections/forms'\nimport type { Translate } from '../fields/types'\nimport type { SubmissionDescriptor, SubmissionValue } from '../submissions/types'\nimport { asFieldTranslate } from '../translations/server'\nimport type { RichTextBodyOption } from './body/serializeBody'\nimport type { ActionRegistry } from './registry'\nimport type { ActionInstance } from './runActions'\nimport { runActions } from './runActions'\n\nexport const ACTIONS_TASK_SLUG = 'form-builder-actions'\n\n/** Input the dispatch path enqueues; the handler re-loads everything else from the DB. */\nexport type ActionsTaskInput = { 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<void> => {\n\tconst { input, registry, payload, req, richText } = args\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\treq,\n\t\t})\n\t\t.catch(() => null)\n\tif (!form) {\n\t\treturn\n\t}\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\tconst locale = typeof submission.locale === 'string' ? submission.locale : (req?.locale ?? 'en')\n\tconst t: Translate = asFieldTranslate(req?.i18n?.t ?? ((key: string) => key))\n\n\tawait 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}\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,SAMzB;CACpB,MAAM,EAAE,OAAO,UAAU,SAAS,KAAK,aAAa;CACpD,MAAM,OAAO,MAAM,QACjB,SAAS;EACT,YAAY;EACZ,IAAI,MAAM;EACV,OAAO;EACP,gBAAgB;EAChB;CACD,CAAC,EACA,YAAY,IAAI;CAClB,IAAI,CAAC,MACJ;CAED,MAAM,aAAa,MAAM,QACvB,SAAS;EACT,YAAY;EACZ,IAAI,MAAM;EACV,OAAO;EACP,gBAAgB;EAChB;CACD,CAAC,EACA,YAAY,IAAI;CAClB,IAAI,CAAC,YACJ;CAGD,MAAM,SAAS,OAAO,WAAW,WAAW,WAAW,WAAW,SAAU,KAAK,UAAU;CAC3F,MAAM,IAAe,iBAAiB,KAAK,MAAM,OAAO,QAAgB,IAAI;CAE5E,MAAM,WAAW;EAChB,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;AACF;;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"}
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 } 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<void> => {\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\tawait 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}\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,SAMzB;CACpB,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;CAOD,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;CAGD,MAAM,IAAe,iBAAiB,KAAK,MAAM,OAAO,QAAgB,IAAI;CAE5E,MAAM,WAAW;EAChB,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;AACF;;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"}
@@ -11,6 +11,15 @@ type AggregateFormResponsesArgs = {
11
11
  req?: PayloadRequest; /** Safety cap on submissions scanned; beyond it the result is flagged `truncated`. Default 10000. */
12
12
  maxSubmissions?: number; /** Page size for the submission scan. Default 500. */
13
13
  pageSize?: number;
14
+ /**
15
+ * Source-resolved options by field name (an option-source poll's results field). They replace
16
+ * the instance's authored options for bucket seeding, order, and labels, and mark the field
17
+ * enumerable for the default field selection.
18
+ */
19
+ resolvedOptions?: Record<string, {
20
+ value: string;
21
+ label: string;
22
+ }[]>;
14
23
  };
15
24
  /** True when a field declares non-empty options (a choice field safe to aggregate publicly). */
16
25
  declare const fieldHasOptions: (field: FormFieldInstance) => boolean;
@@ -1,22 +1,16 @@
1
+ import { isNamedField } from "../fields/fieldKey.js";
2
+ import { instanceOptionsOf } from "../fields/instanceOptions.js";
1
3
  import { aggregateRowsForFields } from "./aggregateRows.js";
2
4
  import { FORMS_SLUG } from "../collections/forms.js";
3
5
  import { FORM_SUBMISSIONS_SLUG } from "../collections/formSubmissions.js";
4
6
  //#region src/aggregation/aggregateResponses.ts
5
- const optionsOf = (field) => {
6
- if (!Array.isArray(field.options)) return;
7
- const options = field.options.filter((option) => typeof option?.value === "string").map((option) => ({
8
- value: String(option.value),
9
- label: option.label ?? String(option.value)
10
- }));
11
- return options.length > 0 ? options : void 0;
12
- };
13
7
  /** True when a field declares non-empty options (a choice field safe to aggregate publicly). */
14
- const fieldHasOptions = (field) => optionsOf(field) !== void 0;
15
- const metaFor = (field) => ({
8
+ const fieldHasOptions = (field) => instanceOptionsOf(field) !== void 0;
9
+ const metaFor = (field, resolved) => ({
16
10
  field: field.name,
17
11
  label: field.label ?? field.name,
18
12
  fieldType: field.blockType,
19
- options: optionsOf(field)
13
+ options: resolved && resolved.length > 0 ? resolved : instanceOptionsOf(field)
20
14
  });
21
15
  /**
22
16
  * Aggregate submission responses for a form. Loads the form for current labels + option order, then pages
@@ -25,7 +19,7 @@ const metaFor = (field) => ({
25
19
  * `truncated`. Returns one `FieldAggregation` per requested field (or per enumerable field by default).
26
20
  */
27
21
  const aggregateFormResponses = async (args) => {
28
- const { payload, formId, fields, status = "complete", req, maxSubmissions = 1e4, pageSize = 500 } = args;
22
+ const { payload, formId, fields, status = "complete", req, maxSubmissions = 1e4, pageSize = 500, resolvedOptions } = args;
29
23
  const form = await payload.findByID({
30
24
  collection: FORMS_SLUG,
31
25
  id: formId,
@@ -35,9 +29,9 @@ const aggregateFormResponses = async (args) => {
35
29
  }).catch(() => null);
36
30
  if (!form || !Array.isArray(form.fields)) return [];
37
31
  const instances = form.fields;
38
- const selected = fields ? instances.filter((field) => fields.includes(field.name)) : instances.filter((field) => optionsOf(field) !== void 0);
32
+ const selected = fields ? instances.filter((field) => isNamedField(field) && fields.includes(field.name)) : instances.filter((field) => isNamedField(field) && (instanceOptionsOf(field) !== void 0 || (resolvedOptions?.[field.name]?.length ?? 0) > 0));
39
33
  if (selected.length === 0) return [];
40
- const metas = selected.map(metaFor);
34
+ const metas = selected.map((field) => metaFor(field, resolvedOptions?.[field.name]));
41
35
  const where = { and: [{ form: { equals: formId } }, ...status === "all" ? [] : [{ status: { equals: status } }]] };
42
36
  const rows = [];
43
37
  let page = 1;
@@ -1 +1 @@
1
- {"version":3,"file":"aggregateResponses.js","names":[],"sources":["../../src/aggregation/aggregateResponses.ts"],"sourcesContent":["import type { Payload, PayloadRequest } from 'payload'\nimport { FORM_SUBMISSIONS_SLUG } from '../collections/formSubmissions'\nimport { FORMS_SLUG } from '../collections/forms'\nimport type { FormFieldInstance } from '../submissions/types'\nimport { aggregateRowsForFields } from './aggregateRows'\nimport type { AggregationRow, FieldAggregation, FieldMeta, SubmissionStatusFilter } from './types'\n\nexport type AggregateFormResponsesArgs = {\n\tpayload: Payload\n\tformId: number | string\n\t/** Field machine names to aggregate. Defaults to every field that declares options (enumerable). */\n\tfields?: string[]\n\t/** Submission status to count. Defaults to `complete`. */\n\tstatus?: SubmissionStatusFilter\n\treq?: PayloadRequest\n\t/** Safety cap on submissions scanned; beyond it the result is flagged `truncated`. Default 10000. */\n\tmaxSubmissions?: number\n\t/** Page size for the submission scan. Default 500. */\n\tpageSize?: number\n}\n\nconst optionsOf = (field: FormFieldInstance): { value: string; label: string }[] | undefined => {\n\tif (!Array.isArray(field.options)) {\n\t\treturn undefined\n\t}\n\tconst options = (field.options as Array<{ label?: string; value?: string }>)\n\t\t.filter((option) => typeof option?.value === 'string')\n\t\t.map((option) => ({ value: String(option.value), label: option.label ?? String(option.value) }))\n\treturn options.length > 0 ? options : undefined\n}\n\n/** True when a field declares non-empty options (a choice field safe to aggregate publicly). */\nexport const fieldHasOptions = (field: FormFieldInstance): boolean => optionsOf(field) !== undefined\n\nconst metaFor = (field: FormFieldInstance): FieldMeta => ({\n\tfield: field.name,\n\tlabel: field.label ?? field.name,\n\tfieldType: field.blockType,\n\toptions: optionsOf(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} = 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((field) => fields.includes(field.name))\n\t\t: instances.filter((field) => optionsOf(field) !== undefined)\n\tif (selected.length === 0) {\n\t\treturn []\n\t}\n\tconst metas = selected.map(metaFor)\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":";;;;AAqBA,MAAM,aAAa,UAA6E;CAC/F,IAAI,CAAC,MAAM,QAAQ,MAAM,OAAO,GAC/B;CAED,MAAM,UAAW,MAAM,QACrB,QAAQ,WAAW,OAAO,QAAQ,UAAU,QAAQ,EACpD,KAAK,YAAY;EAAE,OAAO,OAAO,OAAO,KAAK;EAAG,OAAO,OAAO,SAAS,OAAO,OAAO,KAAK;CAAE,EAAE;CAChG,OAAO,QAAQ,SAAS,IAAI,UAAU,KAAA;AACvC;;AAGA,MAAa,mBAAmB,UAAsC,UAAU,KAAK,MAAM,KAAA;AAE3F,MAAM,WAAW,WAAyC;CACzD,OAAO,MAAM;CACb,OAAO,MAAM,SAAS,MAAM;CAC5B,WAAW,MAAM;CACjB,SAAS,UAAU,KAAK;AACzB;;;;;;;AAQA,MAAa,yBAAyB,OACrC,SACiC;CACjC,MAAM,EACL,SACA,QACA,QACA,SAAS,YACT,KACA,iBAAiB,KACjB,WAAW,QACR;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,QAAQ,UAAU,OAAO,SAAS,MAAM,IAAI,CAAC,IACvD,UAAU,QAAQ,UAAU,UAAU,KAAK,MAAM,KAAA,CAAS;CAC7D,IAAI,SAAS,WAAW,GACvB,OAAO,CAAC;CAET,MAAM,QAAQ,SAAS,IAAI,OAAO;CAGlC,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"}
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"}