@10x-media/form-builder 0.1.0-beta.6 → 0.1.0-beta.8

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 (174) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/dist/actions/body/serializeBody.d.ts +3 -1
  3. package/dist/actions/body/serializeBody.js.map +1 -1
  4. package/dist/actions/buildActionBlocks.js +12 -9
  5. package/dist/actions/buildActionBlocks.js.map +1 -1
  6. package/dist/actions/builtin/confirmation.js +13 -68
  7. package/dist/actions/builtin/confirmation.js.map +1 -1
  8. package/dist/actions/builtin/emailAction.d.ts +17 -0
  9. package/dist/actions/builtin/emailAction.js +83 -0
  10. package/dist/actions/builtin/emailAction.js.map +1 -0
  11. package/dist/actions/builtin/emailTeam.js +14 -74
  12. package/dist/actions/builtin/emailTeam.js.map +1 -1
  13. package/dist/actions/builtin/index.d.ts +9 -12
  14. package/dist/actions/builtin/index.js +11 -11
  15. package/dist/actions/builtin/index.js.map +1 -1
  16. package/dist/actions/defineAction.d.ts +2 -2
  17. package/dist/actions/defineAction.js.map +1 -1
  18. package/dist/actions/emailRecipients.js +75 -31
  19. package/dist/actions/emailRecipients.js.map +1 -1
  20. package/dist/actions/registry.d.ts +2 -1
  21. package/dist/actions/registry.js +2 -6
  22. package/dist/actions/registry.js.map +1 -1
  23. package/dist/actions/task.js +5 -3
  24. package/dist/actions/task.js.map +1 -1
  25. package/dist/calc/evaluate.js +1 -2
  26. package/dist/calc/evaluate.js.map +1 -1
  27. package/dist/calc/normalizeCalc.js +1 -2
  28. package/dist/calc/normalizeCalc.js.map +1 -1
  29. package/dist/calc/types.js.map +1 -1
  30. package/dist/collections/buttonFields.js +1 -1
  31. package/dist/collections/formSubmissions.js +2 -1
  32. package/dist/collections/formSubmissions.js.map +1 -1
  33. package/dist/collections/forms.js +32 -100
  34. package/dist/collections/forms.js.map +1 -1
  35. package/dist/collections/formsEndpoints.js +101 -0
  36. package/dist/collections/formsEndpoints.js.map +1 -0
  37. package/dist/conditions/evaluate.js +1 -1
  38. package/dist/conditions/evaluate.js.map +1 -1
  39. package/dist/conditions/fieldTypes.d.ts +3 -1
  40. package/dist/conditions/fieldTypes.js +4 -1
  41. package/dist/conditions/fieldTypes.js.map +1 -1
  42. package/dist/conditions/normalizeConditions.js +18 -5
  43. package/dist/conditions/normalizeConditions.js.map +1 -1
  44. package/dist/consent/captureConsent.d.ts +7 -0
  45. package/dist/consent/captureConsent.js +7 -0
  46. package/dist/consent/captureConsent.js.map +1 -1
  47. package/dist/consent/consentSourcesField.d.ts +4 -0
  48. package/dist/consent/consentSourcesField.js +5 -1
  49. package/dist/consent/consentSourcesField.js.map +1 -1
  50. package/dist/consent/resolveConsentEntries.d.ts +1 -1
  51. package/dist/consent/resolveConsentEntries.js +1 -4
  52. package/dist/consent/resolveConsentEntries.js.map +1 -1
  53. package/dist/consent/resolveConsentSourcesRequest.js +0 -1
  54. package/dist/consent/resolveConsentSourcesRequest.js.map +1 -1
  55. package/dist/consent/resolveConsentStatements.d.ts +1 -1
  56. package/dist/consent/resolveConsentStatements.js.map +1 -1
  57. package/dist/consent/types.d.ts +9 -1
  58. package/dist/email/departmentsField.js +1 -1
  59. package/dist/exports/react.d.ts +5 -4
  60. package/dist/exports/react.js +3 -2
  61. package/dist/exports/rsc.js +1 -1
  62. package/dist/exports/types.d.ts +1 -1
  63. package/dist/fields/builtin/email.js +1 -1
  64. package/dist/fields/builtin/email.js.map +1 -1
  65. package/dist/fields/builtin/message.js +1 -1
  66. package/dist/fields/builtin/repeater.js +1 -1
  67. package/dist/fields/builtin/select.js +1 -1
  68. package/dist/fields/registry.d.ts +2 -1
  69. package/dist/fields/registry.js +2 -6
  70. package/dist/fields/registry.js.map +1 -1
  71. package/dist/fields/sharedConfig.js +1 -1
  72. package/dist/flow/normalizeFlow.js +7 -3
  73. package/dist/flow/normalizeFlow.js.map +1 -1
  74. package/dist/index.d.ts +4 -201
  75. package/dist/index.js +11 -4
  76. package/dist/index.js.map +1 -1
  77. package/dist/options.d.ts +229 -0
  78. package/dist/plugin/applyRegistryConfig.d.ts +11 -0
  79. package/dist/plugin/applyRegistryConfig.js +20 -0
  80. package/dist/plugin/applyRegistryConfig.js.map +1 -0
  81. package/dist/plugin/registerCollections.js +2 -1
  82. package/dist/plugin/registerCollections.js.map +1 -1
  83. package/dist/poll/buildPollOptionSourceFields.js +2 -2
  84. package/dist/poll/buildPollOptionSourceFields.js.map +1 -1
  85. package/dist/poll/definePollOptionSource.d.ts +2 -2
  86. package/dist/poll/definePollOptionSource.js.map +1 -1
  87. package/dist/poll/pollTypeRegistry.js +2 -4
  88. package/dist/poll/pollTypeRegistry.js.map +1 -1
  89. package/dist/poll/registry.d.ts +2 -1
  90. package/dist/poll/registry.js +2 -6
  91. package/dist/poll/registry.js.map +1 -1
  92. package/dist/poll/resolvePollOptionsRequest.js +0 -1
  93. package/dist/poll/resolvePollOptionsRequest.js.map +1 -1
  94. package/dist/prefill/valuesFromSearchParams.js +2 -7
  95. package/dist/prefill/valuesFromSearchParams.js.map +1 -1
  96. package/dist/react/Form.d.ts +39 -3
  97. package/dist/react/Form.js +148 -164
  98. package/dist/react/Form.js.map +1 -1
  99. package/dist/react/FormContext.d.ts +7 -1
  100. package/dist/react/FormContext.js.map +1 -1
  101. package/dist/react/FormFields.d.ts +18 -0
  102. package/dist/react/FormFields.js +114 -0
  103. package/dist/react/FormFields.js.map +1 -0
  104. package/dist/react/FormLayout.d.ts +4 -2
  105. package/dist/react/FormLayout.js +3 -2
  106. package/dist/react/FormLayout.js.map +1 -1
  107. package/dist/react/Poll.js +15 -5
  108. package/dist/react/Poll.js.map +1 -1
  109. package/dist/react/presentation/DialogSurface.js +1 -4
  110. package/dist/react/presentation/DialogSurface.js.map +1 -1
  111. package/dist/react/renderers/calculation.js +1 -3
  112. package/dist/react/renderers/calculation.js.map +1 -1
  113. package/dist/react/renderers/checkbox.js +1 -3
  114. package/dist/react/renderers/checkbox.js.map +1 -1
  115. package/dist/react/renderers/consent.js +2 -3
  116. package/dist/react/renderers/consent.js.map +1 -1
  117. package/dist/react/renderers/date.js +1 -3
  118. package/dist/react/renderers/date.js.map +1 -1
  119. package/dist/react/renderers/email.js +1 -3
  120. package/dist/react/renderers/email.js.map +1 -1
  121. package/dist/react/renderers/file.js +6 -7
  122. package/dist/react/renderers/file.js.map +1 -1
  123. package/dist/react/renderers/message.js +8 -2
  124. package/dist/react/renderers/message.js.map +1 -1
  125. package/dist/react/renderers/number.js +1 -3
  126. package/dist/react/renderers/number.js.map +1 -1
  127. package/dist/react/renderers/region.js +1 -3
  128. package/dist/react/renderers/region.js.map +1 -1
  129. package/dist/react/renderers/repeater.js +20 -4
  130. package/dist/react/renderers/repeater.js.map +1 -1
  131. package/dist/react/renderers/select.js +1 -3
  132. package/dist/react/renderers/select.js.map +1 -1
  133. package/dist/react/renderers/text.js +1 -3
  134. package/dist/react/renderers/text.js.map +1 -1
  135. package/dist/react/renderers/textarea.js +1 -3
  136. package/dist/react/renderers/textarea.js.map +1 -1
  137. package/dist/react/state.d.ts +3 -0
  138. package/dist/react/state.js +1 -0
  139. package/dist/react/state.js.map +1 -1
  140. package/dist/spam/constants.js +7 -1
  141. package/dist/spam/constants.js.map +1 -1
  142. package/dist/spam/resolveSpam.js +2 -1
  143. package/dist/spam/resolveSpam.js.map +1 -1
  144. package/dist/spam/spamGuard.js +22 -6
  145. package/dist/spam/spamGuard.js.map +1 -1
  146. package/dist/spam/types.d.ts +17 -2
  147. package/dist/submissions/coerceBoolean.js +14 -0
  148. package/dist/submissions/coerceBoolean.js.map +1 -0
  149. package/dist/submissions/runSubmission.js +7 -7
  150. package/dist/submissions/runSubmission.js.map +1 -1
  151. package/dist/submissions/validateSubmission.js +3 -2
  152. package/dist/submissions/validateSubmission.js.map +1 -1
  153. package/dist/translations/de.js +10 -0
  154. package/dist/translations/de.js.map +1 -1
  155. package/dist/translations/en.js +10 -0
  156. package/dist/translations/en.js.map +1 -1
  157. package/dist/translations/keys.d.ts +10 -0
  158. package/dist/translations/keys.js +10 -0
  159. package/dist/translations/keys.js.map +1 -1
  160. package/dist/translations/server.js +8 -1
  161. package/dist/translations/server.js.map +1 -1
  162. package/dist/uploads/captureFileRef.d.ts +10 -3
  163. package/dist/uploads/captureFileRef.js +18 -9
  164. package/dist/uploads/captureFileRef.js.map +1 -1
  165. package/dist/validation/builtin/email.js +1 -1
  166. package/dist/validation/builtin/email.js.map +1 -1
  167. package/dist/validation/builtin/notAlreadySubmitted.js +1 -0
  168. package/dist/validation/builtin/notAlreadySubmitted.js.map +1 -1
  169. package/dist/validation/emailPattern.js +10 -0
  170. package/dist/validation/emailPattern.js.map +1 -0
  171. package/dist/validation/registry.d.ts +2 -1
  172. package/dist/validation/registry.js +2 -9
  173. package/dist/validation/registry.js.map +1 -1
  174. package/package.json +4 -4
@@ -1 +1 @@
1
- {"version":3,"file":"resolveSpam.js","names":[],"sources":["../../src/spam/resolveSpam.ts"],"sourcesContent":["import { DEFAULT_HONEYPOT_FIELD } from './constants'\nimport { defaultIdentify } from './identify'\nimport { createKvRateLimiter } from './rateLimiter'\nimport type {\n\tRateLimitConfig,\n\tResolvedRateLimit,\n\tResolvedSpamConfig,\n\tSpamConfig,\n\tSpamOption,\n} from './types'\n\nconst DEFAULT_WINDOW = 60_000\nconst DEFAULT_SUBMISSION_MAX = 5\nconst DEFAULT_UPLOAD_MAX = 20\n\nconst resolveRateLimit = (\n\toption: false | RateLimitConfig | undefined,\n\tdefaultMax: number\n): false | ResolvedRateLimit => {\n\tif (option === false) {\n\t\treturn false\n\t}\n\tconst cfg = option ?? {}\n\treturn {\n\t\twindow: cfg.window ?? DEFAULT_WINDOW,\n\t\tmax: cfg.max ?? defaultMax,\n\t\tlimiter: cfg.limiter ?? createKvRateLimiter(),\n\t}\n}\n\n/**\n * Resolve the `spam` plugin option into a concrete config. `false` disables the whole subsystem.\n * Otherwise honeypot + both rate limits default on, captcha + metadata capture default off, and the\n * identity seam defaults to user id / trusted IP header.\n */\nexport const resolveSpamConfig = (option: SpamOption | undefined): ResolvedSpamConfig | false => {\n\tif (option === false) {\n\t\treturn false\n\t}\n\tconst cfg: SpamConfig = option ?? {}\n\tconst ipHeader = cfg.ipHeader ?? 'x-forwarded-for'\n\treturn {\n\t\thoneypot:\n\t\t\tcfg.honeypot === false\n\t\t\t\t? false\n\t\t\t\t: { fieldName: cfg.honeypot?.fieldName ?? DEFAULT_HONEYPOT_FIELD },\n\t\trateLimit: resolveRateLimit(cfg.rateLimit, DEFAULT_SUBMISSION_MAX),\n\t\tuploadRateLimit: resolveRateLimit(cfg.uploadRateLimit, DEFAULT_UPLOAD_MAX),\n\t\tcaptcha: cfg.captcha,\n\t\tidentify: cfg.identify ?? defaultIdentify(ipHeader),\n\t\tipHeader,\n\t\tmetadata: { ip: cfg.metadata?.ip ?? false, ua: cfg.metadata?.ua ?? false },\n\t}\n}\n"],"mappings":";;;;AAWA,MAAM,iBAAiB;AACvB,MAAM,yBAAyB;AAC/B,MAAM,qBAAqB;AAE3B,MAAM,oBACL,QACA,eAC+B;CAC/B,IAAI,WAAW,OACd,OAAO;CAER,MAAM,MAAM,UAAU,CAAC;CACvB,OAAO;EACN,QAAQ,IAAI,UAAU;EACtB,KAAK,IAAI,OAAO;EAChB,SAAS,IAAI,WAAW,oBAAoB;CAC7C;AACD;;;;;;AAOA,MAAa,qBAAqB,WAA+D;CAChG,IAAI,WAAW,OACd,OAAO;CAER,MAAM,MAAkB,UAAU,CAAC;CACnC,MAAM,WAAW,IAAI,YAAY;CACjC,OAAO;EACN,UACC,IAAI,aAAa,QACd,QACA,EAAE,WAAW,IAAI,UAAU,aAAA,UAAoC;EACnE,WAAW,iBAAiB,IAAI,WAAW,sBAAsB;EACjE,iBAAiB,iBAAiB,IAAI,iBAAiB,kBAAkB;EACzE,SAAS,IAAI;EACb,UAAU,IAAI,YAAY,gBAAgB,QAAQ;EAClD;EACA,UAAU;GAAE,IAAI,IAAI,UAAU,MAAM;GAAO,IAAI,IAAI,UAAU,MAAM;EAAM;CAC1E;AACD"}
1
+ {"version":3,"file":"resolveSpam.js","names":[],"sources":["../../src/spam/resolveSpam.ts"],"sourcesContent":["import { DEFAULT_HONEYPOT_FIELD } from './constants'\nimport { defaultIdentify } from './identify'\nimport { createKvRateLimiter } from './rateLimiter'\nimport type {\n\tRateLimitConfig,\n\tResolvedRateLimit,\n\tResolvedSpamConfig,\n\tSpamConfig,\n\tSpamOption,\n} from './types'\n\nconst DEFAULT_WINDOW = 60_000\nconst DEFAULT_SUBMISSION_MAX = 5\nconst DEFAULT_UPLOAD_MAX = 20\n\nconst resolveRateLimit = (\n\toption: false | RateLimitConfig | undefined,\n\tdefaultMax: number\n): false | ResolvedRateLimit => {\n\tif (option === false) {\n\t\treturn false\n\t}\n\tconst cfg = option ?? {}\n\treturn {\n\t\twindow: cfg.window ?? DEFAULT_WINDOW,\n\t\tmax: cfg.max ?? defaultMax,\n\t\tlimiter: cfg.limiter ?? createKvRateLimiter(),\n\t}\n}\n\n/**\n * Resolve the `spam` plugin option into a concrete config. `false` disables the whole subsystem.\n * Otherwise honeypot + both rate limits default on, captcha + metadata capture default off, and the\n * identity seam defaults to user id / trusted IP header.\n */\nexport const resolveSpamConfig = (option: SpamOption | undefined): ResolvedSpamConfig | false => {\n\tif (option === false) {\n\t\treturn false\n\t}\n\tconst cfg: SpamConfig = option ?? {}\n\tconst ipHeader = cfg.ipHeader ?? 'x-forwarded-for'\n\treturn {\n\t\thoneypot:\n\t\t\tcfg.honeypot === false\n\t\t\t\t? false\n\t\t\t\t: { fieldName: cfg.honeypot?.fieldName ?? DEFAULT_HONEYPOT_FIELD },\n\t\trateLimit: resolveRateLimit(cfg.rateLimit, DEFAULT_SUBMISSION_MAX),\n\t\tuploadRateLimit: resolveRateLimit(cfg.uploadRateLimit, DEFAULT_UPLOAD_MAX),\n\t\tcaptcha: cfg.captcha,\n\t\tidentify: cfg.identify ?? defaultIdentify(ipHeader),\n\t\tipHeader,\n\t\tmetadata: { ip: cfg.metadata?.ip ?? false, ua: cfg.metadata?.ua ?? false },\n\t\tuploadOwnership: cfg.uploadOwnership ?? 'lenient',\n\t}\n}\n"],"mappings":";;;;AAWA,MAAM,iBAAiB;AACvB,MAAM,yBAAyB;AAC/B,MAAM,qBAAqB;AAE3B,MAAM,oBACL,QACA,eAC+B;CAC/B,IAAI,WAAW,OACd,OAAO;CAER,MAAM,MAAM,UAAU,CAAC;CACvB,OAAO;EACN,QAAQ,IAAI,UAAU;EACtB,KAAK,IAAI,OAAO;EAChB,SAAS,IAAI,WAAW,oBAAoB;CAC7C;AACD;;;;;;AAOA,MAAa,qBAAqB,WAA+D;CAChG,IAAI,WAAW,OACd,OAAO;CAER,MAAM,MAAkB,UAAU,CAAC;CACnC,MAAM,WAAW,IAAI,YAAY;CACjC,OAAO;EACN,UACC,IAAI,aAAa,QACd,QACA,EAAE,WAAW,IAAI,UAAU,aAAA,UAAoC;EACnE,WAAW,iBAAiB,IAAI,WAAW,sBAAsB;EACjE,iBAAiB,iBAAiB,IAAI,iBAAiB,kBAAkB;EACzE,SAAS,IAAI;EACb,UAAU,IAAI,YAAY,gBAAgB,QAAQ;EAClD;EACA,UAAU;GAAE,IAAI,IAAI,UAAU,MAAM;GAAO,IAAI,IAAI,UAAU,MAAM;EAAM;EACzE,iBAAiB,IAAI,mBAAmB;CACzC;AACD"}
@@ -1,10 +1,17 @@
1
1
  import { keys } from "../translations/keys.js";
2
2
  import { asTranslate } from "../translations/server.js";
3
3
  import { firstHop } from "./clientIp.js";
4
- import { IDENTITY_CONTEXT_KEY } from "./constants.js";
4
+ import { HONEYPOT_VALUE_KEY, IDENTITY_CONTEXT_KEY } from "./constants.js";
5
5
  import { extractReservedValues, isHoneypotTripped } from "./reserved.js";
6
6
  import { APIError } from "payload";
7
7
  //#region src/spam/spamGuard.ts
8
+ let warnedRateLimitSkipped = false;
9
+ /** Warn once per process when rate limiting is configured but skipped for lack of a request identity. */
10
+ const warnRateLimitSkippedOnce = (req) => {
11
+ if (warnedRateLimitSkipped) return;
12
+ warnedRateLimitSkipped = true;
13
+ req.payload.logger?.warn("@10x-media/form-builder: submission rate limiting is configured but skipped for a request with no resolvable identity (no user and no client IP). Set spam.ipHeader for your proxy or provide a custom spam.identify.");
14
+ };
8
15
  /**
9
16
  * Submissions spam guard, prepended before `validateSubmission` in `beforeValidate` so it rejects before
10
17
  * the form load + field validation. On create it: resolves the request identity once (stashing it on
@@ -20,7 +27,12 @@ const buildSpamGuard = (spam) => async ({ data, operation, req }) => {
20
27
  const authenticated = Boolean(req.user);
21
28
  const identity = await spam.identify(req);
22
29
  if (identity != null) req.context[IDENTITY_CONTEXT_KEY] = identity;
23
- if (spam.rateLimit !== false && identity != null) {
30
+ let rateLimitState;
31
+ if (spam.rateLimit === false) rateLimitState = "disabled";
32
+ else if (identity == null) {
33
+ rateLimitState = "skipped-no-identity";
34
+ warnRateLimitSkippedOnce(req);
35
+ } else {
24
36
  const formKey = data.form != null ? String(data.form) : "unknown";
25
37
  const { ok } = await spam.rateLimit.limiter.check({
26
38
  key: `submissions:${formKey}:${identity}`,
@@ -29,11 +41,12 @@ const buildSpamGuard = (spam) => async ({ data, operation, req }) => {
29
41
  req
30
42
  });
31
43
  if (!ok) throw new APIError(t(keys.spamRateLimited), 429);
44
+ rateLimitState = "enforced";
32
45
  }
33
- const honeypotField = spam.honeypot === false ? null : spam.honeypot.fieldName;
34
- const { cleaned, honeypot, captchaToken } = extractReservedValues(data.values ?? [], honeypotField);
46
+ const honeypotKey = spam.honeypot === false ? null : HONEYPOT_VALUE_KEY;
47
+ const { cleaned, honeypot, captchaToken } = extractReservedValues(data.values ?? [], honeypotKey);
35
48
  data.values = cleaned;
36
- if (!authenticated && honeypotField !== null && isHoneypotTripped(honeypot)) throw new APIError(t(keys.spamRejected), 400);
49
+ if (!authenticated && honeypotKey !== null && isHoneypotTripped(honeypot)) throw new APIError(t(keys.spamRejected), 400);
37
50
  const captchaChecked = Boolean(spam.captcha) && !authenticated;
38
51
  if (spam.captcha && !authenticated) {
39
52
  if (!(typeof captchaToken === "string" && captchaToken.length > 0 ? await spam.captcha.verify({
@@ -43,7 +56,10 @@ const buildSpamGuard = (spam) => async ({ data, operation, req }) => {
43
56
  }
44
57
  const serverMeta = {
45
58
  at: (/* @__PURE__ */ new Date()).toISOString(),
46
- spam: { captcha: captchaChecked ? "passed" : "skipped" }
59
+ spam: {
60
+ captcha: captchaChecked ? "passed" : "skipped",
61
+ rateLimit: rateLimitState
62
+ }
47
63
  };
48
64
  if (spam.metadata.ip) {
49
65
  const ip = firstHop(req.headers, spam.ipHeader);
@@ -1 +1 @@
1
- {"version":3,"file":"spamGuard.js","names":[],"sources":["../../src/spam/spamGuard.ts"],"sourcesContent":["import { APIError, type CollectionBeforeValidateHook } from 'payload'\nimport type { SubmissionValue } from '../submissions/types'\nimport { keys } from '../translations/keys'\nimport { asTranslate } from '../translations/server'\nimport { firstHop } from './clientIp'\nimport { IDENTITY_CONTEXT_KEY } from './constants'\nimport { extractReservedValues, isHoneypotTripped } from './reserved'\nimport type { ResolvedSpamConfig } from './types'\n\n/**\n * Submissions spam guard, prepended before `validateSubmission` in `beforeValidate` so it rejects before\n * the form load + field validation. On create it: resolves the request identity once (stashing it on\n * `req.context` for upload-ownership verification downstream); rate-limits per form + identity (429,\n * fail-open when identity is null); strips reserved `values` entries (honeypot, captcha token); rejects a\n * filled honeypot with a generic error; verifies a configured captcha; and writes a server-authoritative\n * `meta` (timestamp + spam signal, with opt-in ip/ua). App-level rate limiting is defense-in-depth that\n * complements edge/CDN/WAF limiting, not a DoS replacement.\n */\nexport const buildSpamGuard =\n\t(spam: ResolvedSpamConfig): CollectionBeforeValidateHook =>\n\tasync ({ data, operation, req }) => {\n\t\tif (operation !== 'create' || !data) {\n\t\t\treturn data\n\t\t}\n\t\tconst t = asTranslate(req.i18n.t)\n\t\t// Honeypot + captcha are anti-bot measures for the anonymous public path; an authenticated request\n\t\t// (admin panel, logged-in API client) is already gated by auth and never carries a decoy/token, so\n\t\t// checking it would falsely reject legitimate authed creates. Rate-limit + ownership still apply.\n\t\tconst authenticated = Boolean(req.user)\n\n\t\tconst identity = await spam.identify(req)\n\t\tif (identity != null) {\n\t\t\treq.context[IDENTITY_CONTEXT_KEY] = identity\n\t\t}\n\n\t\tif (spam.rateLimit !== false && identity != null) {\n\t\t\tconst formKey = data.form != null ? String(data.form) : 'unknown'\n\t\t\tconst { ok } = await spam.rateLimit.limiter.check({\n\t\t\t\tkey: `submissions:${formKey}:${identity}`,\n\t\t\t\tmax: spam.rateLimit.max,\n\t\t\t\twindow: spam.rateLimit.window,\n\t\t\t\treq,\n\t\t\t})\n\t\t\tif (!ok) {\n\t\t\t\tthrow new APIError(t(keys.spamRateLimited), 429)\n\t\t\t}\n\t\t}\n\n\t\tconst honeypotField = spam.honeypot === false ? null : spam.honeypot.fieldName\n\t\tconst values = (data.values as SubmissionValue[] | undefined) ?? []\n\t\tconst { cleaned, honeypot, captchaToken } = extractReservedValues(values, honeypotField)\n\t\tdata.values = cleaned\n\n\t\tif (!authenticated && honeypotField !== null && isHoneypotTripped(honeypot)) {\n\t\t\tthrow new APIError(t(keys.spamRejected), 400)\n\t\t}\n\n\t\tconst captchaChecked = Boolean(spam.captcha) && !authenticated\n\t\tif (spam.captcha && !authenticated) {\n\t\t\tconst passed =\n\t\t\t\ttypeof captchaToken === 'string' && captchaToken.length > 0\n\t\t\t\t\t? await spam.captcha.verify({ token: captchaToken, req }).catch(() => false)\n\t\t\t\t\t: false\n\t\t\tif (!passed) {\n\t\t\t\tthrow new APIError(t(keys.spamCaptchaFailed), 400)\n\t\t\t}\n\t\t}\n\n\t\tconst serverMeta: Record<string, unknown> = {\n\t\t\tat: new Date().toISOString(),\n\t\t\tspam: { captcha: captchaChecked ? 'passed' : 'skipped' },\n\t\t}\n\t\tif (spam.metadata.ip) {\n\t\t\tconst ip = firstHop(req.headers, spam.ipHeader)\n\t\t\tif (ip) {\n\t\t\t\tserverMeta.ip = ip\n\t\t\t}\n\t\t}\n\t\tif (spam.metadata.ua) {\n\t\t\tconst ua = req.headers?.get('user-agent')\n\t\t\tif (ua) {\n\t\t\t\tserverMeta.ua = ua\n\t\t\t}\n\t\t}\n\t\t// Preserve an authenticated (trusted) caller's own `meta` keys, with server fields winning. Anonymous\n\t\t// `meta` is never trusted, so it is discarded.\n\t\tconst clientMeta =\n\t\t\tauthenticated && data.meta != null && typeof data.meta === 'object'\n\t\t\t\t? (data.meta as Record<string, unknown>)\n\t\t\t\t: {}\n\t\tdata.meta = { ...clientMeta, ...serverMeta }\n\n\t\treturn data\n\t}\n"],"mappings":";;;;;;;;;;;;;;;;AAkBA,MAAa,kBACX,SACD,OAAO,EAAE,MAAM,WAAW,UAAU;CACnC,IAAI,cAAc,YAAY,CAAC,MAC9B,OAAO;CAER,MAAM,IAAI,YAAY,IAAI,KAAK,CAAC;CAIhC,MAAM,gBAAgB,QAAQ,IAAI,IAAI;CAEtC,MAAM,WAAW,MAAM,KAAK,SAAS,GAAG;CACxC,IAAI,YAAY,MACf,IAAI,QAAQ,wBAAwB;CAGrC,IAAI,KAAK,cAAc,SAAS,YAAY,MAAM;EACjD,MAAM,UAAU,KAAK,QAAQ,OAAO,OAAO,KAAK,IAAI,IAAI;EACxD,MAAM,EAAE,OAAO,MAAM,KAAK,UAAU,QAAQ,MAAM;GACjD,KAAK,eAAe,QAAQ,GAAG;GAC/B,KAAK,KAAK,UAAU;GACpB,QAAQ,KAAK,UAAU;GACvB;EACD,CAAC;EACD,IAAI,CAAC,IACJ,MAAM,IAAI,SAAS,EAAE,KAAK,eAAe,GAAG,GAAG;CAEjD;CAEA,MAAM,gBAAgB,KAAK,aAAa,QAAQ,OAAO,KAAK,SAAS;CAErE,MAAM,EAAE,SAAS,UAAU,iBAAiB,sBAD5B,KAAK,UAA4C,CAAC,GACQ,aAAa;CACvF,KAAK,SAAS;CAEd,IAAI,CAAC,iBAAiB,kBAAkB,QAAQ,kBAAkB,QAAQ,GACzE,MAAM,IAAI,SAAS,EAAE,KAAK,YAAY,GAAG,GAAG;CAG7C,MAAM,iBAAiB,QAAQ,KAAK,OAAO,KAAK,CAAC;CACjD,IAAI,KAAK,WAAW,CAAC;MAKhB,EAHH,OAAO,iBAAiB,YAAY,aAAa,SAAS,IACvD,MAAM,KAAK,QAAQ,OAAO;GAAE,OAAO;GAAc;EAAI,CAAC,EAAE,YAAY,KAAK,IACzE,QAEH,MAAM,IAAI,SAAS,EAAE,KAAK,iBAAiB,GAAG,GAAG;CAAA;CAInD,MAAM,aAAsC;EAC3C,qBAAI,IAAI,KAAK,GAAE,YAAY;EAC3B,MAAM,EAAE,SAAS,iBAAiB,WAAW,UAAU;CACxD;CACA,IAAI,KAAK,SAAS,IAAI;EACrB,MAAM,KAAK,SAAS,IAAI,SAAS,KAAK,QAAQ;EAC9C,IAAI,IACH,WAAW,KAAK;CAElB;CACA,IAAI,KAAK,SAAS,IAAI;EACrB,MAAM,KAAK,IAAI,SAAS,IAAI,YAAY;EACxC,IAAI,IACH,WAAW,KAAK;CAElB;CAOA,KAAK,OAAO;EAAE,GAHb,iBAAiB,KAAK,QAAQ,QAAQ,OAAO,KAAK,SAAS,WACvD,KAAK,OACN,CAAC;EACwB,GAAG;CAAW;CAE3C,OAAO;AACR"}
1
+ {"version":3,"file":"spamGuard.js","names":[],"sources":["../../src/spam/spamGuard.ts"],"sourcesContent":["import { APIError, type CollectionBeforeValidateHook, type PayloadRequest } from 'payload'\nimport type { SubmissionValue } from '../submissions/types'\nimport { keys } from '../translations/keys'\nimport { asTranslate } from '../translations/server'\nimport { firstHop } from './clientIp'\nimport { HONEYPOT_VALUE_KEY, IDENTITY_CONTEXT_KEY } from './constants'\nimport { extractReservedValues, isHoneypotTripped } from './reserved'\nimport type { ResolvedSpamConfig } from './types'\n\nlet warnedRateLimitSkipped = false\n\n/** Warn once per process when rate limiting is configured but skipped for lack of a request identity. */\nconst warnRateLimitSkippedOnce = (req: PayloadRequest): void => {\n\tif (warnedRateLimitSkipped) {\n\t\treturn\n\t}\n\twarnedRateLimitSkipped = true\n\treq.payload.logger?.warn(\n\t\t'@10x-media/form-builder: submission rate limiting is configured but skipped for a request with no resolvable identity (no user and no client IP). Set spam.ipHeader for your proxy or provide a custom spam.identify.'\n\t)\n}\n\n/**\n * Submissions spam guard, prepended before `validateSubmission` in `beforeValidate` so it rejects before\n * the form load + field validation. On create it: resolves the request identity once (stashing it on\n * `req.context` for upload-ownership verification downstream); rate-limits per form + identity (429,\n * fail-open when identity is null); strips reserved `values` entries (honeypot, captcha token); rejects a\n * filled honeypot with a generic error; verifies a configured captcha; and writes a server-authoritative\n * `meta` (timestamp + spam signal, with opt-in ip/ua). App-level rate limiting is defense-in-depth that\n * complements edge/CDN/WAF limiting, not a DoS replacement.\n */\nexport const buildSpamGuard =\n\t(spam: ResolvedSpamConfig): CollectionBeforeValidateHook =>\n\tasync ({ data, operation, req }) => {\n\t\tif (operation !== 'create' || !data) {\n\t\t\treturn data\n\t\t}\n\t\tconst t = asTranslate(req.i18n.t)\n\t\t// Honeypot + captcha are anti-bot measures for the anonymous public path; an authenticated request\n\t\t// (admin panel, logged-in API client) is already gated by auth and never carries a decoy/token, so\n\t\t// checking it would falsely reject legitimate authed creates. Rate-limit + ownership still apply.\n\t\tconst authenticated = Boolean(req.user)\n\n\t\tconst identity = await spam.identify(req)\n\t\tif (identity != null) {\n\t\t\treq.context[IDENTITY_CONTEXT_KEY] = identity\n\t\t}\n\n\t\tlet rateLimitState: 'enforced' | 'skipped-no-identity' | 'disabled'\n\t\tif (spam.rateLimit === false) {\n\t\t\trateLimitState = 'disabled'\n\t\t} else if (identity == null) {\n\t\t\t// Fail open (an unidentifiable submitter cannot be keyed) but never silently: record it on\n\t\t\t// meta.spam and warn once, so a deployment with no proxy or a misconfigured ipHeader is visible.\n\t\t\trateLimitState = 'skipped-no-identity'\n\t\t\twarnRateLimitSkippedOnce(req)\n\t\t} else {\n\t\t\tconst formKey = data.form != null ? String(data.form) : 'unknown'\n\t\t\tconst { ok } = await spam.rateLimit.limiter.check({\n\t\t\t\tkey: `submissions:${formKey}:${identity}`,\n\t\t\t\tmax: spam.rateLimit.max,\n\t\t\t\twindow: spam.rateLimit.window,\n\t\t\t\treq,\n\t\t\t})\n\t\t\tif (!ok) {\n\t\t\t\tthrow new APIError(t(keys.spamRateLimited), 429)\n\t\t\t}\n\t\t\trateLimitState = 'enforced'\n\t\t}\n\n\t\t// The decoy rides a fixed reserved key (not its cosmetic DOM name), so a real field sharing that\n\t\t// name is never stripped or mistaken for the honeypot. `spam.honeypot.fieldName` is now only the\n\t\t// client's DOM-input name hint and no longer participates in server matching.\n\t\tconst honeypotKey = spam.honeypot === false ? null : HONEYPOT_VALUE_KEY\n\t\tconst values = (data.values as SubmissionValue[] | undefined) ?? []\n\t\tconst { cleaned, honeypot, captchaToken } = extractReservedValues(values, honeypotKey)\n\t\tdata.values = cleaned\n\n\t\tif (!authenticated && honeypotKey !== null && isHoneypotTripped(honeypot)) {\n\t\t\tthrow new APIError(t(keys.spamRejected), 400)\n\t\t}\n\n\t\tconst captchaChecked = Boolean(spam.captcha) && !authenticated\n\t\tif (spam.captcha && !authenticated) {\n\t\t\tconst passed =\n\t\t\t\ttypeof captchaToken === 'string' && captchaToken.length > 0\n\t\t\t\t\t? await spam.captcha.verify({ token: captchaToken, req }).catch(() => false)\n\t\t\t\t\t: false\n\t\t\tif (!passed) {\n\t\t\t\tthrow new APIError(t(keys.spamCaptchaFailed), 400)\n\t\t\t}\n\t\t}\n\n\t\tconst serverMeta: Record<string, unknown> = {\n\t\t\tat: new Date().toISOString(),\n\t\t\tspam: { captcha: captchaChecked ? 'passed' : 'skipped', rateLimit: rateLimitState },\n\t\t}\n\t\tif (spam.metadata.ip) {\n\t\t\tconst ip = firstHop(req.headers, spam.ipHeader)\n\t\t\tif (ip) {\n\t\t\t\tserverMeta.ip = ip\n\t\t\t}\n\t\t}\n\t\tif (spam.metadata.ua) {\n\t\t\tconst ua = req.headers?.get('user-agent')\n\t\t\tif (ua) {\n\t\t\t\tserverMeta.ua = ua\n\t\t\t}\n\t\t}\n\t\t// Preserve an authenticated (trusted) caller's own `meta` keys, with server fields winning. Anonymous\n\t\t// `meta` is never trusted, so it is discarded.\n\t\tconst clientMeta =\n\t\t\tauthenticated && data.meta != null && typeof data.meta === 'object'\n\t\t\t\t? (data.meta as Record<string, unknown>)\n\t\t\t\t: {}\n\t\tdata.meta = { ...clientMeta, ...serverMeta }\n\n\t\treturn data\n\t}\n"],"mappings":";;;;;;;AASA,IAAI,yBAAyB;;AAG7B,MAAM,4BAA4B,QAA8B;CAC/D,IAAI,wBACH;CAED,yBAAyB;CACzB,IAAI,QAAQ,QAAQ,KACnB,uNACD;AACD;;;;;;;;;;AAWA,MAAa,kBACX,SACD,OAAO,EAAE,MAAM,WAAW,UAAU;CACnC,IAAI,cAAc,YAAY,CAAC,MAC9B,OAAO;CAER,MAAM,IAAI,YAAY,IAAI,KAAK,CAAC;CAIhC,MAAM,gBAAgB,QAAQ,IAAI,IAAI;CAEtC,MAAM,WAAW,MAAM,KAAK,SAAS,GAAG;CACxC,IAAI,YAAY,MACf,IAAI,QAAQ,wBAAwB;CAGrC,IAAI;CACJ,IAAI,KAAK,cAAc,OACtB,iBAAiB;MACX,IAAI,YAAY,MAAM;EAG5B,iBAAiB;EACjB,yBAAyB,GAAG;CAC7B,OAAO;EACN,MAAM,UAAU,KAAK,QAAQ,OAAO,OAAO,KAAK,IAAI,IAAI;EACxD,MAAM,EAAE,OAAO,MAAM,KAAK,UAAU,QAAQ,MAAM;GACjD,KAAK,eAAe,QAAQ,GAAG;GAC/B,KAAK,KAAK,UAAU;GACpB,QAAQ,KAAK,UAAU;GACvB;EACD,CAAC;EACD,IAAI,CAAC,IACJ,MAAM,IAAI,SAAS,EAAE,KAAK,eAAe,GAAG,GAAG;EAEhD,iBAAiB;CAClB;CAKA,MAAM,cAAc,KAAK,aAAa,QAAQ,OAAO;CAErD,MAAM,EAAE,SAAS,UAAU,iBAAiB,sBAD5B,KAAK,UAA4C,CAAC,GACQ,WAAW;CACrF,KAAK,SAAS;CAEd,IAAI,CAAC,iBAAiB,gBAAgB,QAAQ,kBAAkB,QAAQ,GACvE,MAAM,IAAI,SAAS,EAAE,KAAK,YAAY,GAAG,GAAG;CAG7C,MAAM,iBAAiB,QAAQ,KAAK,OAAO,KAAK,CAAC;CACjD,IAAI,KAAK,WAAW,CAAC;MAKhB,EAHH,OAAO,iBAAiB,YAAY,aAAa,SAAS,IACvD,MAAM,KAAK,QAAQ,OAAO;GAAE,OAAO;GAAc;EAAI,CAAC,EAAE,YAAY,KAAK,IACzE,QAEH,MAAM,IAAI,SAAS,EAAE,KAAK,iBAAiB,GAAG,GAAG;CAAA;CAInD,MAAM,aAAsC;EAC3C,qBAAI,IAAI,KAAK,GAAE,YAAY;EAC3B,MAAM;GAAE,SAAS,iBAAiB,WAAW;GAAW,WAAW;EAAe;CACnF;CACA,IAAI,KAAK,SAAS,IAAI;EACrB,MAAM,KAAK,SAAS,IAAI,SAAS,KAAK,QAAQ;EAC9C,IAAI,IACH,WAAW,KAAK;CAElB;CACA,IAAI,KAAK,SAAS,IAAI;EACrB,MAAM,KAAK,IAAI,SAAS,IAAI,YAAY;EACxC,IAAI,IACH,WAAW,KAAK;CAElB;CAOA,KAAK,OAAO;EAAE,GAHb,iBAAiB,KAAK,QAAQ,QAAQ,OAAO,KAAK,SAAS,WACvD,KAAK,OACN,CAAC;EACwB,GAAG;CAAW;CAE3C,OAAO;AACR"}
@@ -37,13 +37,27 @@ type SpamMetadataConfig = {
37
37
  ua?: boolean;
38
38
  };
39
39
  type SpamConfig = {
40
- /** Honeypot decoy. Default on. `false` disables. */honeypot?: false | {
40
+ /**
41
+ * Honeypot decoy. Default on. `false` disables. `fieldName` is accepted for backward compatibility
42
+ * but no longer participates in server matching: the decoy is submitted under a fixed reserved key,
43
+ * so it never collides with a real field, and its DOM input name is set on the client via `<Form honeypot>`.
44
+ */
45
+ honeypot?: false | {
41
46
  fieldName?: string;
42
47
  }; /** Per-identity rate limit on submission create. Default on (60s window, max 5). `false` disables. */
43
48
  rateLimit?: false | RateLimitConfig; /** Per-identity rate limit on upload create. Default on (60s window, max 20). `false` disables. */
44
49
  uploadRateLimit?: false | RateLimitConfig; /** A captcha provider (none by default): `turnstileProvider`/`recaptchaProvider`/`hcaptchaProvider` or a custom adapter. */
45
50
  captcha?: CaptchaProvider; /** Identity resolution for rate-limiting, upload ownership, and (future) poll dedup. Default: user id, else trusted IP header. */
46
- identify?: IdentifyFn; /** Header read for the client IP (proxy-dependent, best-effort). Default 'x-forwarded-for'. */
51
+ identify?: IdentifyFn;
52
+ /**
53
+ * How strictly a submitted file's upload-ownership is enforced. `'lenient'` (default) only rejects a
54
+ * stamped upload when the submitter is identified and does not match, and passes an owned upload
55
+ * through when the submitter cannot be identified (fail-open, consistent with rate-limiting).
56
+ * `'strict'` also rejects a stamped upload whenever the submitter cannot be identified, so a
57
+ * deployment that always identifies requests (a proxy setting the trusted IP header, or a custom
58
+ * `identify`) never lets an unverifiable claim capture an owned upload.
59
+ */
60
+ uploadOwnership?: 'lenient' | 'strict'; /** Header read for the client IP (proxy-dependent, best-effort). Default 'x-forwarded-for'. */
47
61
  ipHeader?: string; /** Opt-in capture of client metadata onto the submission `meta`. Off by default. */
48
62
  metadata?: SpamMetadataConfig;
49
63
  };
@@ -67,6 +81,7 @@ type ResolvedSpamConfig = {
67
81
  ip: boolean;
68
82
  ua: boolean;
69
83
  };
84
+ uploadOwnership: 'lenient' | 'strict';
70
85
  };
71
86
  //#endregion
72
87
  export { CaptchaProvider, CaptchaVerifyArgs, IdentifyFn, RateLimitCheckArgs, RateLimitConfig, RateLimitResult, RateLimiter, ResolvedSpamConfig, SpamConfig, SpamMetadataConfig, SpamOption };
@@ -0,0 +1,14 @@
1
+ //#region src/submissions/coerceBoolean.ts
2
+ /** Strings that count as boolean true. Shared by prefill and submit-time coercion so they never diverge. */
3
+ const TRUTHY_STRINGS = new Set([
4
+ "true",
5
+ "1",
6
+ "on",
7
+ "yes"
8
+ ]);
9
+ /** True when a string is an affirmative token (trimmed, case-insensitive); everything else is false. */
10
+ const isTruthyString = (value) => TRUTHY_STRINGS.has(value.trim().toLowerCase());
11
+ //#endregion
12
+ export { isTruthyString };
13
+
14
+ //# sourceMappingURL=coerceBoolean.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coerceBoolean.js","names":[],"sources":["../../src/submissions/coerceBoolean.ts"],"sourcesContent":["/** Strings that count as boolean true. Shared by prefill and submit-time coercion so they never diverge. */\nexport const TRUTHY_STRINGS = new Set(['true', '1', 'on', 'yes'])\n\n/** True when a string is an affirmative token (trimmed, case-insensitive); everything else is false. */\nexport const isTruthyString = (value: string): boolean =>\n\tTRUTHY_STRINGS.has(value.trim().toLowerCase())\n"],"mappings":";;AACA,MAAa,iBAAiB,IAAI,IAAI;CAAC;CAAQ;CAAK;CAAM;AAAK,CAAC;;AAGhE,MAAa,kBAAkB,UAC9B,eAAe,IAAI,MAAM,KAAK,EAAE,YAAY,CAAC"}
@@ -5,6 +5,7 @@ import { evaluateCondition } from "../conditions/evaluate.js";
5
5
  import { captureConsent } from "../consent/captureConsent.js";
6
6
  import { captureFileRef } from "../uploads/captureFileRef.js";
7
7
  import { runValidation } from "../validation/runValidation.js";
8
+ import { isTruthyString } from "./coerceBoolean.js";
8
9
  //#region src/submissions/runSubmission.ts
9
10
  const SUBMISSION_WIDTHS = new Set([
10
11
  "full",
@@ -37,10 +38,7 @@ const coerce = (kind, value) => {
37
38
  return Number.isNaN(next) ? value : next;
38
39
  }
39
40
  if (kind === "boolean") {
40
- if (typeof value === "string") {
41
- const normalized = value.trim().toLowerCase();
42
- return !(normalized === "" || normalized === "false" || normalized === "0" || normalized === "off" || normalized === "no");
43
- }
41
+ if (typeof value === "string") return isTruthyString(value);
44
42
  return Boolean(value);
45
43
  }
46
44
  if (kind === "text") return typeof value === "string" ? value : String(value);
@@ -67,7 +65,7 @@ const optionLabelsFor = (instance) => {
67
65
  * validated, never stored, and a client-sent value under their name is dropped.
68
66
  */
69
67
  const runSubmission = async (input) => {
70
- const { fields, values, registry, ruleRegistry, consentEntries, consentSnapshot, locale, t, operation, req, payload, formId, uploadSlug, expectedOwner } = input;
68
+ const { fields, values, registry, ruleRegistry, consentEntries, consentSnapshot, locale, t, operation, req, payload, formId, uploadSlug, expectedOwner, strictUploadOwnership } = input;
71
69
  const incoming = new Map(values.map((entry) => [entry.field, entry.value]));
72
70
  const coercedAnswers = {};
73
71
  const coercedByName = /* @__PURE__ */ new Map();
@@ -147,7 +145,8 @@ const runSubmission = async (input) => {
147
145
  uploadId: value,
148
146
  config: fileFieldConfigOf(instance),
149
147
  req,
150
- expectedOwner
148
+ expectedOwner,
149
+ strict: strictUploadOwnership
151
150
  });
152
151
  if (!captured.ok) {
153
152
  errors.push({
@@ -244,7 +243,8 @@ const runSubmission = async (input) => {
244
243
  uploadId: subValue,
245
244
  config: fileFieldConfigOf(subField),
246
245
  req,
247
- expectedOwner
246
+ expectedOwner,
247
+ strict: strictUploadOwnership
248
248
  });
249
249
  if (!captured.ok) {
250
250
  errors.push({
@@ -1 +1 @@
1
- {"version":3,"file":"runSubmission.js","names":[],"sources":["../../src/submissions/runSubmission.ts"],"sourcesContent":["import type { Payload, PayloadRequest } from 'payload'\nimport { calcExpressionOf, computeCalcFields } from '../calc/computeCalcFields'\nimport { evaluateCondition } from '../conditions/evaluate'\nimport type { ConsentProof, ConsentSnapshotMode } from '../consent/captureConsent'\nimport { captureConsent } from '../consent/captureConsent'\nimport type { ConsentSourceEntry } from '../consent/types'\nimport { isNamedField } from '../fields/fieldKey'\nimport type { FieldTypeRegistry } from '../fields/registry'\nimport type { Translate } from '../fields/types'\nimport { keys } from '../translations/keys'\nimport { captureFileRef } from '../uploads/captureFileRef'\nimport type { FileFieldConfig, FileRefError } from '../uploads/types'\nimport type { ValidationRuleRegistry } from '../validation/registry'\nimport { runValidation } from '../validation/runValidation'\nimport type {\n\tFormFieldInstance,\n\tSubmissionDescriptor,\n\tSubmissionFieldError,\n\tSubmissionValue,\n\tSubmissionWidth,\n} from './types'\n\nconst SUBMISSION_WIDTHS: ReadonlySet<string> = new Set<SubmissionWidth>([\n\t'full',\n\t'half',\n\t'third',\n\t'twoThirds',\n])\n\n/** The field instance's authored layout width, or undefined if unset/unrecognized (renders full). */\nexport const widthOf = (instance: FormFieldInstance): SubmissionWidth | undefined =>\n\ttypeof instance.width === 'string' && SUBMISSION_WIDTHS.has(instance.width)\n\t\t? (instance.width as SubmissionWidth)\n\t\t: undefined\n\nconst errorKeyFor = (code: FileRefError): string => {\n\tif (code === 'mimeType') {\n\t\treturn keys.validationFileMimeType\n\t}\n\tif (code === 'tooLarge') {\n\t\treturn keys.validationFileTooLarge\n\t}\n\treturn keys.validationFileMissing\n}\n\n/** Normalize the authored `mimeTypes` (a `hasMany` text field) to a `string[]`. */\nconst mimeTypesOf = (raw: unknown): string[] | undefined => {\n\tif (!Array.isArray(raw)) {\n\t\treturn undefined\n\t}\n\tconst out = (raw as unknown[]).filter((entry): entry is string => typeof entry === 'string')\n\treturn out.length > 0 ? out : undefined\n}\n\nconst fileFieldConfigOf = (instance: FormFieldInstance): FileFieldConfig => ({\n\tmimeTypes: mimeTypesOf(instance.mimeTypes),\n\tmaxSize: typeof instance.maxSize === 'number' ? instance.maxSize : undefined,\n})\n\nconst isEmpty = (value: unknown): boolean =>\n\tvalue == null || value === '' || (Array.isArray(value) && value.length === 0)\n\nconst coerce = (kind: string, value: unknown): unknown => {\n\tif (value == null) {\n\t\treturn value\n\t}\n\tif (kind === 'number') {\n\t\tconst next = typeof value === 'number' ? value : Number(value)\n\t\treturn Number.isNaN(next) ? value : next\n\t}\n\tif (kind === 'boolean') {\n\t\t// A genuine boolean from the renderer passes through; a raw client string is parsed strictly so that\n\t\t// \"false\"/\"0\"/\"off\"/\"no\"/\"\" are not silently truthy (this matters for the required-consent check).\n\t\tif (typeof value === 'string') {\n\t\t\tconst normalized = value.trim().toLowerCase()\n\t\t\treturn !(\n\t\t\t\tnormalized === '' ||\n\t\t\t\tnormalized === 'false' ||\n\t\t\t\tnormalized === '0' ||\n\t\t\t\tnormalized === 'off' ||\n\t\t\t\tnormalized === 'no'\n\t\t\t)\n\t\t}\n\t\treturn Boolean(value)\n\t}\n\tif (kind === 'text') {\n\t\treturn typeof value === 'string' ? value : String(value)\n\t}\n\treturn value\n}\n\nconst optionLabelsFor = (instance: FormFieldInstance): Record<string, string> | undefined => {\n\tconst options = instance.options\n\tif (!Array.isArray(options)) {\n\t\treturn undefined\n\t}\n\tconst map: Record<string, string> = {}\n\tfor (const option of options as Array<{ label?: string; value?: string }>) {\n\t\tif (typeof option?.value === 'string') {\n\t\t\tmap[option.value] = option.label ?? option.value\n\t\t}\n\t}\n\treturn Object.keys(map).length > 0 ? map : undefined\n}\n\nexport type ConsentProofEntry = { field: string } & ConsentProof\n\nexport type RunSubmissionInput = {\n\tfields: FormFieldInstance[]\n\tvalues: SubmissionValue[]\n\tregistry: FieldTypeRegistry\n\truleRegistry: ValidationRuleRegistry\n\t/**\n\t * The host's consent sources, resolved once by the caller (which owns the request-scoped\n\t * resolver and how a failure surfaces) and read here to build each consent proof.\n\t */\n\tconsentEntries?: ConsentSourceEntry[]\n\t/** What each consent proof snapshots of the agreed wording (plugin option `consent.snapshot`). */\n\tconsentSnapshot?: ConsentSnapshotMode\n\tlocale: string\n\tt: Translate\n\toperation: 'create' | 'update'\n\treq?: PayloadRequest\n\tpayload?: Payload\n\tformId?: number | string\n\t/**\n\t * The plugin-configured uploads collection slug. The block's stamped `collection` is for the\n\t * client only; the server resolves the slug from plugin config and fails closed without one.\n\t */\n\tuploadSlug?: string\n\t/** Resolved request identity, verified against an upload's `owner` stamp when a file field is captured. */\n\texpectedOwner?: string\n}\n\nexport type RunSubmissionResult = {\n\terrors: SubmissionFieldError[]\n\tvalues: SubmissionValue[]\n\tdescriptors: SubmissionDescriptor[]\n\tconsent: ConsentProofEntry[]\n}\n\n/**\n * Pure submission core, two-pass: first coerce every answered field to its typed kind (so cross-field\n * rules see coerced siblings), then validate each field through `runValidation` (required, intrinsic\n * facet, declarative rules), snapshotting a localized descriptor per answered field. Calc fields are\n * the trust boundary: their client-sent values are never seeded, their values are derived from their\n * expressions over the coerced answers, and those derived values are authoritative everywhere downstream\n * (conditions, validation, storage). Conditions gate the second pass against these effective answers: a\n * field whose `visibleWhen` is false is skipped entirely (never validated, never stored, so a client-sent\n * value for it is ignored), and a visible field whose `validateWhen` is false stores its value but skips\n * validation. A visible calc field stores its derived value and is never validated. Display-only field\n * types (value kind 'none', e.g. message) and nameless (bare) rows are skipped in both passes: never\n * validated, never stored, and a client-sent value under their name is dropped.\n */\nexport const runSubmission = async (input: RunSubmissionInput): Promise<RunSubmissionResult> => {\n\tconst {\n\t\tfields,\n\t\tvalues,\n\t\tregistry,\n\t\truleRegistry,\n\t\tconsentEntries,\n\t\tconsentSnapshot,\n\t\tlocale,\n\t\tt,\n\t\toperation,\n\t\treq,\n\t\tpayload,\n\t\tformId,\n\t\tuploadSlug,\n\t\texpectedOwner,\n\t} = input\n\tconst incoming = new Map(values.map((entry) => [entry.field, entry.value]))\n\n\tconst coercedAnswers: Record<string, unknown> = {}\n\tconst coercedByName = new Map<string, unknown>()\n\tfor (const instance of fields) {\n\t\tconst definition = registry.get(instance.blockType)\n\t\t// Never seed a calc field's client value: its value is derived below, so the client cannot influence it (even for a self-referencing expression).\n\t\t// A display-only ('none' kind) field carries no value at all, so a client-sent value under its name is dropped here.\n\t\t// A nameless (bare) row has no key to read a value under.\n\t\tif (\n\t\t\t!definition ||\n\t\t\tdefinition.value === 'none' ||\n\t\t\tcalcExpressionOf(instance) ||\n\t\t\t!isNamedField(instance)\n\t\t) {\n\t\t\tcontinue\n\t\t}\n\t\tconst raw = incoming.get(instance.name)\n\t\t// A consent field's \"not agreed\" state is semantically meaningful: treat a missing value as\n\t\t// `false` so the intrinsic validate can enforce required-agreement (not optional = must be true).\n\t\t// A repeater with no rows is coerced to [] so validate() can check minRows. The empty-guard\n\t\t// below must not skip repeaters: isEmpty([]) is true, but [] still needs to reach validate()\n\t\t// so a minRows > 0 constraint correctly rejects a zero-row submission.\n\t\tconst effectiveRaw =\n\t\t\tinstance.blockType === 'consent' && isEmpty(raw)\n\t\t\t\t? false\n\t\t\t\t: instance.blockType === 'repeater' && isEmpty(raw)\n\t\t\t\t\t? []\n\t\t\t\t\t: raw\n\t\tif (isEmpty(effectiveRaw) && instance.blockType !== 'repeater') {\n\t\t\tcontinue\n\t\t}\n\t\tconst value = coerce(definition.value, effectiveRaw)\n\t\tcoercedAnswers[instance.name] = value\n\t\tcoercedByName.set(instance.name, value)\n\t}\n\n\t// Calc values are authoritative everywhere downstream (conditions, validation, storage), never the client-sent value.\n\tconst effective = computeCalcFields(fields, coercedAnswers)\n\n\tconst errors: SubmissionFieldError[] = []\n\tconst outValues: SubmissionValue[] = []\n\tconst descriptors: SubmissionDescriptor[] = []\n\tconst consentProofs: ConsentProofEntry[] = []\n\tconst now = new Date().toISOString()\n\n\tfor (const instance of fields) {\n\t\tconst definition = registry.get(instance.blockType)\n\t\t// A 'none'-kind (display-only) field is never validated and never stored: no value, no descriptor.\n\t\t// A nameless (bare) row has no key to store under, so it is skipped the same way.\n\t\tif (!definition || definition.value === 'none' || !isNamedField(instance)) {\n\t\t\tcontinue\n\t\t}\n\t\tconst raw = incoming.get(instance.name)\n\t\tconst value = coercedByName.has(instance.name) ? coercedByName.get(instance.name) : raw\n\n\t\tif (!evaluateCondition(instance.visibleWhen, effective)) {\n\t\t\tcontinue\n\t\t}\n\n\t\t// A calc field's value is server-derived and always valid: skip validation and the client value entirely, storing the computed result.\n\t\tif (calcExpressionOf(instance)) {\n\t\t\toutValues.push({ field: instance.name, value: effective[instance.name] })\n\t\t\tdescriptors.push({\n\t\t\t\tfield: instance.name,\n\t\t\t\tlabel: instance.label ?? instance.name,\n\t\t\t\tfieldType: instance.blockType,\n\t\t\t\t...(widthOf(instance) ? { width: widthOf(instance) } : {}),\n\t\t\t})\n\t\t\tcontinue\n\t\t}\n\n\t\tif (evaluateCondition(instance.validateWhen, effective)) {\n\t\t\tconst { errors: issues } = await runValidation({\n\t\t\t\tfield: instance,\n\t\t\t\tfieldDefinition: definition,\n\t\t\t\tvalue,\n\t\t\t\tfieldType: instance.blockType,\n\t\t\t\truleRegistry,\n\t\t\t\tanswers: effective,\n\t\t\t\tlocale,\n\t\t\t\tt,\n\t\t\t\toperation,\n\t\t\t\tevent: 'submit',\n\t\t\t\tmode: 'server',\n\t\t\t\treq,\n\t\t\t\tpayload,\n\t\t\t\tformId,\n\t\t\t})\n\t\t\tif (issues.length > 0) {\n\t\t\t\tfor (const message of issues) {\n\t\t\t\t\terrors.push({ path: instance.name, message })\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\t// Gate on the field-type definition's value kind, not the blockType literal, so a custom\n\t\t// type registered with value:'file' gets the same server-side capture and enforcement as\n\t\t// the built-in file field (a blockType check would leave it storing the raw client id).\n\t\tif (definition.value === 'file') {\n\t\t\tif (isEmpty(value)) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif (payload) {\n\t\t\t\tif (!uploadSlug) {\n\t\t\t\t\terrors.push({ path: instance.name, message: t(errorKeyFor('missing')) })\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst fileConfig = fileFieldConfigOf(instance)\n\t\t\t\tconst captured = await captureFileRef({\n\t\t\t\t\tpayload,\n\t\t\t\t\tcollectionSlug: uploadSlug,\n\t\t\t\t\tuploadId: value as string | number,\n\t\t\t\t\tconfig: fileConfig,\n\t\t\t\t\treq,\n\t\t\t\t\texpectedOwner,\n\t\t\t\t})\n\t\t\t\tif (!captured.ok) {\n\t\t\t\t\terrors.push({ path: instance.name, message: t(errorKeyFor(captured.code)) })\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\toutValues.push({ field: instance.name, value: captured.ref })\n\t\t\t\tdescriptors.push({\n\t\t\t\t\tfield: instance.name,\n\t\t\t\t\tlabel: instance.label ?? instance.name,\n\t\t\t\t\tfieldType: instance.blockType,\n\t\t\t\t\t...(widthOf(instance) ? { width: widthOf(instance) } : {}),\n\t\t\t\t})\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\toutValues.push({ field: instance.name, value })\n\t\t\tdescriptors.push({\n\t\t\t\tfield: instance.name,\n\t\t\t\tlabel: instance.label ?? instance.name,\n\t\t\t\tfieldType: instance.blockType,\n\t\t\t\t...(widthOf(instance) ? { width: widthOf(instance) } : {}),\n\t\t\t})\n\t\t\tcontinue\n\t\t}\n\n\t\tif (instance.blockType === 'consent' && payload) {\n\t\t\tconst proof = await captureConsent({\n\t\t\t\tfield: instance,\n\t\t\t\tagreed: value === true,\n\t\t\t\tentries: consentEntries ?? [],\n\t\t\t\tpayload,\n\t\t\t\treq,\n\t\t\t\tnow,\n\t\t\t\tsnapshot: consentSnapshot,\n\t\t\t})\n\t\t\tconsentProofs.push({ field: instance.name, ...proof })\n\t\t\tcontinue\n\t\t}\n\n\t\tif (instance.blockType === 'repeater') {\n\t\t\tconst rows = Array.isArray(value) ? (value as Array<Record<string, unknown>>) : []\n\t\t\t// Nameless sub-rows are dropped like top-level ones (bare blocks are excluded from the\n\t\t\t// repeater's subFields config, so any encountered here is stray data).\n\t\t\tconst subFields = (\n\t\t\t\tArray.isArray(instance.subFields) ? (instance.subFields as FormFieldInstance[]) : []\n\t\t\t).filter(isNamedField)\n\n\t\t\t// Per-row sub-field processing. Validation is gated by the sub-field's visibleWhen and\n\t\t\t// validateWhen against the row's own values; errors carry the path\n\t\t\t// fieldName[rowIndex].subFieldName so the client maps them to the right input. File\n\t\t\t// sub-fields additionally cross the server trust boundary: they are captured\n\t\t\t// unconditionally (a crafted request could carry an id for a conditionally-hidden field),\n\t\t\t// so a stored row never holds a raw, unenforced upload id.\n\t\t\tfor (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {\n\t\t\t\tconst row = rows[rowIndex] ?? {}\n\t\t\t\tfor (const subField of subFields) {\n\t\t\t\t\tconst subDef = registry.get(subField.blockType)\n\t\t\t\t\tif (!subDef) continue\n\t\t\t\t\t// Display-only ('none' kind) sub-fields mirror the top-level skip: never validated,\n\t\t\t\t\t// never captured, and stripped from the stored row below so a client-injected value\n\t\t\t\t\t// under their name cannot ride along.\n\t\t\t\t\tif (subDef.value === 'none') continue\n\t\t\t\t\tconst subPath = `${instance.name}[${rowIndex}].${subField.name}`\n\t\t\t\t\tif (\n\t\t\t\t\t\tevaluateCondition(subField.visibleWhen, row) &&\n\t\t\t\t\t\tevaluateCondition(subField.validateWhen, row)\n\t\t\t\t\t) {\n\t\t\t\t\t\tconst { errors: subErrors } = await runValidation({\n\t\t\t\t\t\t\tfield: subField,\n\t\t\t\t\t\t\tfieldDefinition: subDef,\n\t\t\t\t\t\t\tvalue: row[subField.name],\n\t\t\t\t\t\t\tfieldType: subField.blockType,\n\t\t\t\t\t\t\truleRegistry,\n\t\t\t\t\t\t\tanswers: row,\n\t\t\t\t\t\t\tlocale,\n\t\t\t\t\t\t\tt,\n\t\t\t\t\t\t\toperation,\n\t\t\t\t\t\t\tevent: 'submit',\n\t\t\t\t\t\t\tmode: 'server',\n\t\t\t\t\t\t\treq,\n\t\t\t\t\t\t\tpayload,\n\t\t\t\t\t\t\tformId,\n\t\t\t\t\t\t})\n\t\t\t\t\t\tfor (const message of subErrors) {\n\t\t\t\t\t\t\terrors.push({ path: subPath, message })\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Mirror the top-level file capture: the client submits only an upload id, so\n\t\t\t\t\t// re-derive filename/mimeType/filesize from the stored doc and enforce\n\t\t\t\t\t// owner/mime/size against the sub-field's config, failing closed on a missing\n\t\t\t\t\t// uploads slug or any capture rejection. The captured FileRef replaces the raw\n\t\t\t\t\t// id in the row. Without a payload (pure unit context) the value stays verbatim,\n\t\t\t\t\t// exactly like the top-level path.\n\t\t\t\t\tif (subDef.value === 'file' && payload) {\n\t\t\t\t\t\tconst subValue = row[subField.name]\n\t\t\t\t\t\tif (isEmpty(subValue)) {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!uploadSlug) {\n\t\t\t\t\t\t\terrors.push({ path: subPath, message: t(errorKeyFor('missing')) })\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst captured = await captureFileRef({\n\t\t\t\t\t\t\tpayload,\n\t\t\t\t\t\t\tcollectionSlug: uploadSlug,\n\t\t\t\t\t\t\tuploadId: subValue as string | number,\n\t\t\t\t\t\t\tconfig: fileFieldConfigOf(subField),\n\t\t\t\t\t\t\treq,\n\t\t\t\t\t\t\texpectedOwner,\n\t\t\t\t\t\t})\n\t\t\t\t\t\tif (!captured.ok) {\n\t\t\t\t\t\t\terrors.push({ path: subPath, message: t(errorKeyFor(captured.code)) })\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\trow[subField.name] = captured.ref\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (errors.length > 0) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tconst subFieldDescriptors: SubmissionDescriptor[] = subFields.map((sf) => ({\n\t\t\t\tfield: sf.name,\n\t\t\t\tlabel: sf.label ?? sf.name,\n\t\t\t\tfieldType: sf.blockType,\n\t\t\t\t...((sf.options as unknown) ? { optionLabels: optionLabelsFor(sf) ?? undefined } : {}),\n\t\t\t}))\n\n\t\t\t// Strip display-only sub-fields from the stored rows: like top-level 'none' fields they\n\t\t\t// carry no answer, so a client-injected value under their name must never persist.\n\t\t\tconst displayOnly = new Set(\n\t\t\t\tsubFields.filter((sf) => registry.get(sf.blockType)?.value === 'none').map((sf) => sf.name)\n\t\t\t)\n\t\t\tconst storedRows =\n\t\t\t\tdisplayOnly.size === 0\n\t\t\t\t\t? rows\n\t\t\t\t\t: rows.map((row) =>\n\t\t\t\t\t\t\tObject.fromEntries(Object.entries(row).filter(([key]) => !displayOnly.has(key)))\n\t\t\t\t\t\t)\n\n\t\t\toutValues.push({ field: instance.name, value: storedRows })\n\t\t\tdescriptors.push({\n\t\t\t\tfield: instance.name,\n\t\t\t\tlabel: instance.label ?? instance.name,\n\t\t\t\tfieldType: instance.blockType,\n\t\t\t\t...(widthOf(instance) ? { width: widthOf(instance) } : {}),\n\t\t\t\t...(subFieldDescriptors.length > 0 ? { subFieldDescriptors } : {}),\n\t\t\t})\n\t\t\tcontinue\n\t\t}\n\n\t\tif (isEmpty(raw)) {\n\t\t\tcontinue\n\t\t}\n\n\t\toutValues.push({ field: instance.name, value })\n\t\tconst optionLabels = optionLabelsFor(instance)\n\t\tdescriptors.push({\n\t\t\tfield: instance.name,\n\t\t\tlabel: instance.label ?? instance.name,\n\t\t\tfieldType: instance.blockType,\n\t\t\t...(widthOf(instance) ? { width: widthOf(instance) } : {}),\n\t\t\t...(optionLabels ? { optionLabels } : {}),\n\t\t})\n\t}\n\n\treturn { errors, values: outValues, descriptors, consent: consentProofs }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"runSubmission.js","names":[],"sources":["../../src/submissions/runSubmission.ts"],"sourcesContent":["import type { Payload, PayloadRequest } from 'payload'\nimport { calcExpressionOf, computeCalcFields } from '../calc/computeCalcFields'\nimport { evaluateCondition } from '../conditions/evaluate'\nimport type { ConsentProof, ConsentSnapshotMode } from '../consent/captureConsent'\nimport { captureConsent } from '../consent/captureConsent'\nimport type { ConsentSourceEntry } from '../consent/types'\nimport { isNamedField } from '../fields/fieldKey'\nimport type { FieldTypeRegistry } from '../fields/registry'\nimport type { Translate } from '../fields/types'\nimport { keys } from '../translations/keys'\nimport { captureFileRef } from '../uploads/captureFileRef'\nimport type { FileFieldConfig, FileRefError } from '../uploads/types'\nimport type { ValidationRuleRegistry } from '../validation/registry'\nimport { runValidation } from '../validation/runValidation'\nimport { isTruthyString } from './coerceBoolean'\nimport type {\n\tFormFieldInstance,\n\tSubmissionDescriptor,\n\tSubmissionFieldError,\n\tSubmissionValue,\n\tSubmissionWidth,\n} from './types'\n\nconst SUBMISSION_WIDTHS: ReadonlySet<string> = new Set<SubmissionWidth>([\n\t'full',\n\t'half',\n\t'third',\n\t'twoThirds',\n])\n\n/** The field instance's authored layout width, or undefined if unset/unrecognized (renders full). */\nexport const widthOf = (instance: FormFieldInstance): SubmissionWidth | undefined =>\n\ttypeof instance.width === 'string' && SUBMISSION_WIDTHS.has(instance.width)\n\t\t? (instance.width as SubmissionWidth)\n\t\t: undefined\n\nconst errorKeyFor = (code: FileRefError): string => {\n\tif (code === 'mimeType') {\n\t\treturn keys.validationFileMimeType\n\t}\n\tif (code === 'tooLarge') {\n\t\treturn keys.validationFileTooLarge\n\t}\n\treturn keys.validationFileMissing\n}\n\n/** Normalize the authored `mimeTypes` (a `hasMany` text field) to a `string[]`. */\nconst mimeTypesOf = (raw: unknown): string[] | undefined => {\n\tif (!Array.isArray(raw)) {\n\t\treturn undefined\n\t}\n\tconst out = (raw as unknown[]).filter((entry): entry is string => typeof entry === 'string')\n\treturn out.length > 0 ? out : undefined\n}\n\nconst fileFieldConfigOf = (instance: FormFieldInstance): FileFieldConfig => ({\n\tmimeTypes: mimeTypesOf(instance.mimeTypes),\n\tmaxSize: typeof instance.maxSize === 'number' ? instance.maxSize : undefined,\n})\n\nconst isEmpty = (value: unknown): boolean =>\n\tvalue == null || value === '' || (Array.isArray(value) && value.length === 0)\n\nconst coerce = (kind: string, value: unknown): unknown => {\n\tif (value == null) {\n\t\treturn value\n\t}\n\tif (kind === 'number') {\n\t\tconst next = typeof value === 'number' ? value : Number(value)\n\t\treturn Number.isNaN(next) ? value : next\n\t}\n\tif (kind === 'boolean') {\n\t\t// A genuine boolean from the renderer passes through; a raw client string is parsed against the\n\t\t// shared truthy allow-list (also used by prefill), so only affirmative tokens read as true. The\n\t\t// server is the trust boundary for consent: a stray string must never count as agreement.\n\t\tif (typeof value === 'string') {\n\t\t\treturn isTruthyString(value)\n\t\t}\n\t\treturn Boolean(value)\n\t}\n\tif (kind === 'text') {\n\t\treturn typeof value === 'string' ? value : String(value)\n\t}\n\treturn value\n}\n\nconst optionLabelsFor = (instance: FormFieldInstance): Record<string, string> | undefined => {\n\tconst options = instance.options\n\tif (!Array.isArray(options)) {\n\t\treturn undefined\n\t}\n\tconst map: Record<string, string> = {}\n\tfor (const option of options as Array<{ label?: string; value?: string }>) {\n\t\tif (typeof option?.value === 'string') {\n\t\t\tmap[option.value] = option.label ?? option.value\n\t\t}\n\t}\n\treturn Object.keys(map).length > 0 ? map : undefined\n}\n\nexport type ConsentProofEntry = { field: string } & ConsentProof\n\nexport type RunSubmissionInput = {\n\tfields: FormFieldInstance[]\n\tvalues: SubmissionValue[]\n\tregistry: FieldTypeRegistry\n\truleRegistry: ValidationRuleRegistry\n\t/**\n\t * The host's consent sources, resolved once by the caller (which owns the request-scoped\n\t * resolver and how a failure surfaces) and read here to build each consent proof.\n\t */\n\tconsentEntries?: ConsentSourceEntry[]\n\t/** What each consent proof snapshots of the agreed wording (plugin option `consent.snapshot`). */\n\tconsentSnapshot?: ConsentSnapshotMode\n\tlocale: string\n\tt: Translate\n\toperation: 'create' | 'update'\n\treq?: PayloadRequest\n\tpayload?: Payload\n\tformId?: number | string\n\t/**\n\t * The plugin-configured uploads collection slug. The block's stamped `collection` is for the\n\t * client only; the server resolves the slug from plugin config and fails closed without one.\n\t */\n\tuploadSlug?: string\n\t/** Resolved request identity, verified against an upload's `owner` stamp when a file field is captured. */\n\texpectedOwner?: string\n\t/** Plugin `spam.uploadOwnership: 'strict'`: reject an owned upload when the submitter is unidentifiable. */\n\tstrictUploadOwnership?: boolean\n}\n\nexport type RunSubmissionResult = {\n\terrors: SubmissionFieldError[]\n\tvalues: SubmissionValue[]\n\tdescriptors: SubmissionDescriptor[]\n\tconsent: ConsentProofEntry[]\n}\n\n/**\n * Pure submission core, two-pass: first coerce every answered field to its typed kind (so cross-field\n * rules see coerced siblings), then validate each field through `runValidation` (required, intrinsic\n * facet, declarative rules), snapshotting a localized descriptor per answered field. Calc fields are\n * the trust boundary: their client-sent values are never seeded, their values are derived from their\n * expressions over the coerced answers, and those derived values are authoritative everywhere downstream\n * (conditions, validation, storage). Conditions gate the second pass against these effective answers: a\n * field whose `visibleWhen` is false is skipped entirely (never validated, never stored, so a client-sent\n * value for it is ignored), and a visible field whose `validateWhen` is false stores its value but skips\n * validation. A visible calc field stores its derived value and is never validated. Display-only field\n * types (value kind 'none', e.g. message) and nameless (bare) rows are skipped in both passes: never\n * validated, never stored, and a client-sent value under their name is dropped.\n */\nexport const runSubmission = async (input: RunSubmissionInput): Promise<RunSubmissionResult> => {\n\tconst {\n\t\tfields,\n\t\tvalues,\n\t\tregistry,\n\t\truleRegistry,\n\t\tconsentEntries,\n\t\tconsentSnapshot,\n\t\tlocale,\n\t\tt,\n\t\toperation,\n\t\treq,\n\t\tpayload,\n\t\tformId,\n\t\tuploadSlug,\n\t\texpectedOwner,\n\t\tstrictUploadOwnership,\n\t} = input\n\tconst incoming = new Map(values.map((entry) => [entry.field, entry.value]))\n\n\tconst coercedAnswers: Record<string, unknown> = {}\n\tconst coercedByName = new Map<string, unknown>()\n\tfor (const instance of fields) {\n\t\tconst definition = registry.get(instance.blockType)\n\t\t// Never seed a calc field's client value: its value is derived below, so the client cannot influence it (even for a self-referencing expression).\n\t\t// A display-only ('none' kind) field carries no value at all, so a client-sent value under its name is dropped here.\n\t\t// A nameless (bare) row has no key to read a value under.\n\t\tif (\n\t\t\t!definition ||\n\t\t\tdefinition.value === 'none' ||\n\t\t\tcalcExpressionOf(instance) ||\n\t\t\t!isNamedField(instance)\n\t\t) {\n\t\t\tcontinue\n\t\t}\n\t\tconst raw = incoming.get(instance.name)\n\t\t// A consent field's \"not agreed\" state is semantically meaningful: treat a missing value as\n\t\t// `false` so the intrinsic validate can enforce required-agreement (not optional = must be true).\n\t\t// A repeater with no rows is coerced to [] so validate() can check minRows. The empty-guard\n\t\t// below must not skip repeaters: isEmpty([]) is true, but [] still needs to reach validate()\n\t\t// so a minRows > 0 constraint correctly rejects a zero-row submission.\n\t\tconst effectiveRaw =\n\t\t\tinstance.blockType === 'consent' && isEmpty(raw)\n\t\t\t\t? false\n\t\t\t\t: instance.blockType === 'repeater' && isEmpty(raw)\n\t\t\t\t\t? []\n\t\t\t\t\t: raw\n\t\tif (isEmpty(effectiveRaw) && instance.blockType !== 'repeater') {\n\t\t\tcontinue\n\t\t}\n\t\tconst value = coerce(definition.value, effectiveRaw)\n\t\tcoercedAnswers[instance.name] = value\n\t\tcoercedByName.set(instance.name, value)\n\t}\n\n\t// Calc values are authoritative everywhere downstream (conditions, validation, storage), never the client-sent value.\n\tconst effective = computeCalcFields(fields, coercedAnswers)\n\n\tconst errors: SubmissionFieldError[] = []\n\tconst outValues: SubmissionValue[] = []\n\tconst descriptors: SubmissionDescriptor[] = []\n\tconst consentProofs: ConsentProofEntry[] = []\n\tconst now = new Date().toISOString()\n\n\tfor (const instance of fields) {\n\t\tconst definition = registry.get(instance.blockType)\n\t\t// A 'none'-kind (display-only) field is never validated and never stored: no value, no descriptor.\n\t\t// A nameless (bare) row has no key to store under, so it is skipped the same way.\n\t\tif (!definition || definition.value === 'none' || !isNamedField(instance)) {\n\t\t\tcontinue\n\t\t}\n\t\tconst raw = incoming.get(instance.name)\n\t\tconst value = coercedByName.has(instance.name) ? coercedByName.get(instance.name) : raw\n\n\t\tif (!evaluateCondition(instance.visibleWhen, effective)) {\n\t\t\tcontinue\n\t\t}\n\n\t\t// A calc field's value is server-derived and always valid: skip validation and the client value entirely, storing the computed result.\n\t\tif (calcExpressionOf(instance)) {\n\t\t\toutValues.push({ field: instance.name, value: effective[instance.name] })\n\t\t\tdescriptors.push({\n\t\t\t\tfield: instance.name,\n\t\t\t\tlabel: instance.label ?? instance.name,\n\t\t\t\tfieldType: instance.blockType,\n\t\t\t\t...(widthOf(instance) ? { width: widthOf(instance) } : {}),\n\t\t\t})\n\t\t\tcontinue\n\t\t}\n\n\t\tif (evaluateCondition(instance.validateWhen, effective)) {\n\t\t\tconst { errors: issues } = await runValidation({\n\t\t\t\tfield: instance,\n\t\t\t\tfieldDefinition: definition,\n\t\t\t\tvalue,\n\t\t\t\tfieldType: instance.blockType,\n\t\t\t\truleRegistry,\n\t\t\t\tanswers: effective,\n\t\t\t\tlocale,\n\t\t\t\tt,\n\t\t\t\toperation,\n\t\t\t\tevent: 'submit',\n\t\t\t\tmode: 'server',\n\t\t\t\treq,\n\t\t\t\tpayload,\n\t\t\t\tformId,\n\t\t\t})\n\t\t\tif (issues.length > 0) {\n\t\t\t\tfor (const message of issues) {\n\t\t\t\t\terrors.push({ path: instance.name, message })\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\t// Gate on the field-type definition's value kind, not the blockType literal, so a custom\n\t\t// type registered with value:'file' gets the same server-side capture and enforcement as\n\t\t// the built-in file field (a blockType check would leave it storing the raw client id).\n\t\tif (definition.value === 'file') {\n\t\t\tif (isEmpty(value)) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif (payload) {\n\t\t\t\tif (!uploadSlug) {\n\t\t\t\t\terrors.push({ path: instance.name, message: t(errorKeyFor('missing')) })\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst fileConfig = fileFieldConfigOf(instance)\n\t\t\t\tconst captured = await captureFileRef({\n\t\t\t\t\tpayload,\n\t\t\t\t\tcollectionSlug: uploadSlug,\n\t\t\t\t\tuploadId: value as string | number,\n\t\t\t\t\tconfig: fileConfig,\n\t\t\t\t\treq,\n\t\t\t\t\texpectedOwner,\n\t\t\t\t\tstrict: strictUploadOwnership,\n\t\t\t\t})\n\t\t\t\tif (!captured.ok) {\n\t\t\t\t\terrors.push({ path: instance.name, message: t(errorKeyFor(captured.code)) })\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\toutValues.push({ field: instance.name, value: captured.ref })\n\t\t\t\tdescriptors.push({\n\t\t\t\t\tfield: instance.name,\n\t\t\t\t\tlabel: instance.label ?? instance.name,\n\t\t\t\t\tfieldType: instance.blockType,\n\t\t\t\t\t...(widthOf(instance) ? { width: widthOf(instance) } : {}),\n\t\t\t\t})\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\toutValues.push({ field: instance.name, value })\n\t\t\tdescriptors.push({\n\t\t\t\tfield: instance.name,\n\t\t\t\tlabel: instance.label ?? instance.name,\n\t\t\t\tfieldType: instance.blockType,\n\t\t\t\t...(widthOf(instance) ? { width: widthOf(instance) } : {}),\n\t\t\t})\n\t\t\tcontinue\n\t\t}\n\n\t\tif (instance.blockType === 'consent' && payload) {\n\t\t\tconst proof = await captureConsent({\n\t\t\t\tfield: instance,\n\t\t\t\tagreed: value === true,\n\t\t\t\tentries: consentEntries ?? [],\n\t\t\t\tpayload,\n\t\t\t\treq,\n\t\t\t\tnow,\n\t\t\t\tsnapshot: consentSnapshot,\n\t\t\t})\n\t\t\tconsentProofs.push({ field: instance.name, ...proof })\n\t\t\tcontinue\n\t\t}\n\n\t\tif (instance.blockType === 'repeater') {\n\t\t\tconst rows = Array.isArray(value) ? (value as Array<Record<string, unknown>>) : []\n\t\t\t// Nameless sub-rows are dropped like top-level ones (bare blocks are excluded from the\n\t\t\t// repeater's subFields config, so any encountered here is stray data).\n\t\t\tconst subFields = (\n\t\t\t\tArray.isArray(instance.subFields) ? (instance.subFields as FormFieldInstance[]) : []\n\t\t\t).filter(isNamedField)\n\n\t\t\t// Per-row sub-field processing. Validation is gated by the sub-field's visibleWhen and\n\t\t\t// validateWhen against the row's own values; errors carry the path\n\t\t\t// fieldName[rowIndex].subFieldName so the client maps them to the right input. File\n\t\t\t// sub-fields additionally cross the server trust boundary: they are captured\n\t\t\t// unconditionally (a crafted request could carry an id for a conditionally-hidden field),\n\t\t\t// so a stored row never holds a raw, unenforced upload id.\n\t\t\tfor (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {\n\t\t\t\tconst row = rows[rowIndex] ?? {}\n\t\t\t\tfor (const subField of subFields) {\n\t\t\t\t\tconst subDef = registry.get(subField.blockType)\n\t\t\t\t\tif (!subDef) continue\n\t\t\t\t\t// Display-only ('none' kind) sub-fields mirror the top-level skip: never validated,\n\t\t\t\t\t// never captured, and stripped from the stored row below so a client-injected value\n\t\t\t\t\t// under their name cannot ride along.\n\t\t\t\t\tif (subDef.value === 'none') continue\n\t\t\t\t\tconst subPath = `${instance.name}[${rowIndex}].${subField.name}`\n\t\t\t\t\tif (\n\t\t\t\t\t\tevaluateCondition(subField.visibleWhen, row) &&\n\t\t\t\t\t\tevaluateCondition(subField.validateWhen, row)\n\t\t\t\t\t) {\n\t\t\t\t\t\tconst { errors: subErrors } = await runValidation({\n\t\t\t\t\t\t\tfield: subField,\n\t\t\t\t\t\t\tfieldDefinition: subDef,\n\t\t\t\t\t\t\tvalue: row[subField.name],\n\t\t\t\t\t\t\tfieldType: subField.blockType,\n\t\t\t\t\t\t\truleRegistry,\n\t\t\t\t\t\t\tanswers: row,\n\t\t\t\t\t\t\tlocale,\n\t\t\t\t\t\t\tt,\n\t\t\t\t\t\t\toperation,\n\t\t\t\t\t\t\tevent: 'submit',\n\t\t\t\t\t\t\tmode: 'server',\n\t\t\t\t\t\t\treq,\n\t\t\t\t\t\t\tpayload,\n\t\t\t\t\t\t\tformId,\n\t\t\t\t\t\t})\n\t\t\t\t\t\tfor (const message of subErrors) {\n\t\t\t\t\t\t\terrors.push({ path: subPath, message })\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Mirror the top-level file capture: the client submits only an upload id, so\n\t\t\t\t\t// re-derive filename/mimeType/filesize from the stored doc and enforce\n\t\t\t\t\t// owner/mime/size against the sub-field's config, failing closed on a missing\n\t\t\t\t\t// uploads slug or any capture rejection. The captured FileRef replaces the raw\n\t\t\t\t\t// id in the row. Without a payload (pure unit context) the value stays verbatim,\n\t\t\t\t\t// exactly like the top-level path.\n\t\t\t\t\tif (subDef.value === 'file' && payload) {\n\t\t\t\t\t\tconst subValue = row[subField.name]\n\t\t\t\t\t\tif (isEmpty(subValue)) {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!uploadSlug) {\n\t\t\t\t\t\t\terrors.push({ path: subPath, message: t(errorKeyFor('missing')) })\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst captured = await captureFileRef({\n\t\t\t\t\t\t\tpayload,\n\t\t\t\t\t\t\tcollectionSlug: uploadSlug,\n\t\t\t\t\t\t\tuploadId: subValue as string | number,\n\t\t\t\t\t\t\tconfig: fileFieldConfigOf(subField),\n\t\t\t\t\t\t\treq,\n\t\t\t\t\t\t\texpectedOwner,\n\t\t\t\t\t\t\tstrict: strictUploadOwnership,\n\t\t\t\t\t\t})\n\t\t\t\t\t\tif (!captured.ok) {\n\t\t\t\t\t\t\terrors.push({ path: subPath, message: t(errorKeyFor(captured.code)) })\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\trow[subField.name] = captured.ref\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (errors.length > 0) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tconst subFieldDescriptors: SubmissionDescriptor[] = subFields.map((sf) => ({\n\t\t\t\tfield: sf.name,\n\t\t\t\tlabel: sf.label ?? sf.name,\n\t\t\t\tfieldType: sf.blockType,\n\t\t\t\t...((sf.options as unknown) ? { optionLabels: optionLabelsFor(sf) ?? undefined } : {}),\n\t\t\t}))\n\n\t\t\t// Strip display-only sub-fields from the stored rows: like top-level 'none' fields they\n\t\t\t// carry no answer, so a client-injected value under their name must never persist.\n\t\t\tconst displayOnly = new Set(\n\t\t\t\tsubFields.filter((sf) => registry.get(sf.blockType)?.value === 'none').map((sf) => sf.name)\n\t\t\t)\n\t\t\tconst storedRows =\n\t\t\t\tdisplayOnly.size === 0\n\t\t\t\t\t? rows\n\t\t\t\t\t: rows.map((row) =>\n\t\t\t\t\t\t\tObject.fromEntries(Object.entries(row).filter(([key]) => !displayOnly.has(key)))\n\t\t\t\t\t\t)\n\n\t\t\toutValues.push({ field: instance.name, value: storedRows })\n\t\t\tdescriptors.push({\n\t\t\t\tfield: instance.name,\n\t\t\t\tlabel: instance.label ?? instance.name,\n\t\t\t\tfieldType: instance.blockType,\n\t\t\t\t...(widthOf(instance) ? { width: widthOf(instance) } : {}),\n\t\t\t\t...(subFieldDescriptors.length > 0 ? { subFieldDescriptors } : {}),\n\t\t\t})\n\t\t\tcontinue\n\t\t}\n\n\t\tif (isEmpty(raw)) {\n\t\t\tcontinue\n\t\t}\n\n\t\toutValues.push({ field: instance.name, value })\n\t\tconst optionLabels = optionLabelsFor(instance)\n\t\tdescriptors.push({\n\t\t\tfield: instance.name,\n\t\t\tlabel: instance.label ?? instance.name,\n\t\t\tfieldType: instance.blockType,\n\t\t\t...(widthOf(instance) ? { width: widthOf(instance) } : {}),\n\t\t\t...(optionLabels ? { optionLabels } : {}),\n\t\t})\n\t}\n\n\treturn { errors, values: outValues, descriptors, consent: consentProofs }\n}\n"],"mappings":";;;;;;;;;AAuBA,MAAM,oBAAyC,IAAI,IAAqB;CACvE;CACA;CACA;CACA;AACD,CAAC;;AAGD,MAAa,WAAW,aACvB,OAAO,SAAS,UAAU,YAAY,kBAAkB,IAAI,SAAS,KAAK,IACtE,SAAS,QACV,KAAA;AAEJ,MAAM,eAAe,SAA+B;CACnD,IAAI,SAAS,YACZ,OAAO,KAAK;CAEb,IAAI,SAAS,YACZ,OAAO,KAAK;CAEb,OAAO,KAAK;AACb;;AAGA,MAAM,eAAe,QAAuC;CAC3D,IAAI,CAAC,MAAM,QAAQ,GAAG,GACrB;CAED,MAAM,MAAO,IAAkB,QAAQ,UAA2B,OAAO,UAAU,QAAQ;CAC3F,OAAO,IAAI,SAAS,IAAI,MAAM,KAAA;AAC/B;AAEA,MAAM,qBAAqB,cAAkD;CAC5E,WAAW,YAAY,SAAS,SAAS;CACzC,SAAS,OAAO,SAAS,YAAY,WAAW,SAAS,UAAU,KAAA;AACpE;AAEA,MAAM,WAAW,UAChB,SAAS,QAAQ,UAAU,MAAO,MAAM,QAAQ,KAAK,KAAK,MAAM,WAAW;AAE5E,MAAM,UAAU,MAAc,UAA4B;CACzD,IAAI,SAAS,MACZ,OAAO;CAER,IAAI,SAAS,UAAU;EACtB,MAAM,OAAO,OAAO,UAAU,WAAW,QAAQ,OAAO,KAAK;EAC7D,OAAO,OAAO,MAAM,IAAI,IAAI,QAAQ;CACrC;CACA,IAAI,SAAS,WAAW;EAIvB,IAAI,OAAO,UAAU,UACpB,OAAO,eAAe,KAAK;EAE5B,OAAO,QAAQ,KAAK;CACrB;CACA,IAAI,SAAS,QACZ,OAAO,OAAO,UAAU,WAAW,QAAQ,OAAO,KAAK;CAExD,OAAO;AACR;AAEA,MAAM,mBAAmB,aAAoE;CAC5F,MAAM,UAAU,SAAS;CACzB,IAAI,CAAC,MAAM,QAAQ,OAAO,GACzB;CAED,MAAM,MAA8B,CAAC;CACrC,KAAK,MAAM,UAAU,SACpB,IAAI,OAAO,QAAQ,UAAU,UAC5B,IAAI,OAAO,SAAS,OAAO,SAAS,OAAO;CAG7C,OAAO,OAAO,KAAK,GAAG,EAAE,SAAS,IAAI,MAAM,KAAA;AAC5C;;;;;;;;;;;;;;AAqDA,MAAa,gBAAgB,OAAO,UAA4D;CAC/F,MAAM,EACL,QACA,QACA,UACA,cACA,gBACA,iBACA,QACA,GACA,WACA,KACA,SACA,QACA,YACA,eACA,0BACG;CACJ,MAAM,WAAW,IAAI,IAAI,OAAO,KAAK,UAAU,CAAC,MAAM,OAAO,MAAM,KAAK,CAAC,CAAC;CAE1E,MAAM,iBAA0C,CAAC;CACjD,MAAM,gCAAgB,IAAI,IAAqB;CAC/C,KAAK,MAAM,YAAY,QAAQ;EAC9B,MAAM,aAAa,SAAS,IAAI,SAAS,SAAS;EAIlD,IACC,CAAC,cACD,WAAW,UAAU,UACrB,iBAAiB,QAAQ,KACzB,CAAC,aAAa,QAAQ,GAEtB;EAED,MAAM,MAAM,SAAS,IAAI,SAAS,IAAI;EAMtC,MAAM,eACL,SAAS,cAAc,aAAa,QAAQ,GAAG,IAC5C,QACA,SAAS,cAAc,cAAc,QAAQ,GAAG,IAC/C,CAAC,IACD;EACL,IAAI,QAAQ,YAAY,KAAK,SAAS,cAAc,YACnD;EAED,MAAM,QAAQ,OAAO,WAAW,OAAO,YAAY;EACnD,eAAe,SAAS,QAAQ;EAChC,cAAc,IAAI,SAAS,MAAM,KAAK;CACvC;CAGA,MAAM,YAAY,kBAAkB,QAAQ,cAAc;CAE1D,MAAM,SAAiC,CAAC;CACxC,MAAM,YAA+B,CAAC;CACtC,MAAM,cAAsC,CAAC;CAC7C,MAAM,gBAAqC,CAAC;CAC5C,MAAM,uBAAM,IAAI,KAAK,GAAE,YAAY;CAEnC,KAAK,MAAM,YAAY,QAAQ;EAC9B,MAAM,aAAa,SAAS,IAAI,SAAS,SAAS;EAGlD,IAAI,CAAC,cAAc,WAAW,UAAU,UAAU,CAAC,aAAa,QAAQ,GACvE;EAED,MAAM,MAAM,SAAS,IAAI,SAAS,IAAI;EACtC,MAAM,QAAQ,cAAc,IAAI,SAAS,IAAI,IAAI,cAAc,IAAI,SAAS,IAAI,IAAI;EAEpF,IAAI,CAAC,kBAAkB,SAAS,aAAa,SAAS,GACrD;EAID,IAAI,iBAAiB,QAAQ,GAAG;GAC/B,UAAU,KAAK;IAAE,OAAO,SAAS;IAAM,OAAO,UAAU,SAAS;GAAM,CAAC;GACxE,YAAY,KAAK;IAChB,OAAO,SAAS;IAChB,OAAO,SAAS,SAAS,SAAS;IAClC,WAAW,SAAS;IACpB,GAAI,QAAQ,QAAQ,IAAI,EAAE,OAAO,QAAQ,QAAQ,EAAE,IAAI,CAAC;GACzD,CAAC;GACD;EACD;EAEA,IAAI,kBAAkB,SAAS,cAAc,SAAS,GAAG;GACxD,MAAM,EAAE,QAAQ,WAAW,MAAM,cAAc;IAC9C,OAAO;IACP,iBAAiB;IACjB;IACA,WAAW,SAAS;IACpB;IACA,SAAS;IACT;IACA;IACA;IACA,OAAO;IACP,MAAM;IACN;IACA;IACA;GACD,CAAC;GACD,IAAI,OAAO,SAAS,GAAG;IACtB,KAAK,MAAM,WAAW,QACrB,OAAO,KAAK;KAAE,MAAM,SAAS;KAAM;IAAQ,CAAC;IAE7C;GACD;EACD;EAKA,IAAI,WAAW,UAAU,QAAQ;GAChC,IAAI,QAAQ,KAAK,GAChB;GAED,IAAI,SAAS;IACZ,IAAI,CAAC,YAAY;KAChB,OAAO,KAAK;MAAE,MAAM,SAAS;MAAM,SAAS,EAAE,YAAY,SAAS,CAAC;KAAE,CAAC;KACvE;IACD;IAEA,MAAM,WAAW,MAAM,eAAe;KACrC;KACA,gBAAgB;KAChB,UAAU;KACV,QALkB,kBAAkB,QAKnB;KACjB;KACA;KACA,QAAQ;IACT,CAAC;IACD,IAAI,CAAC,SAAS,IAAI;KACjB,OAAO,KAAK;MAAE,MAAM,SAAS;MAAM,SAAS,EAAE,YAAY,SAAS,IAAI,CAAC;KAAE,CAAC;KAC3E;IACD;IACA,UAAU,KAAK;KAAE,OAAO,SAAS;KAAM,OAAO,SAAS;IAAI,CAAC;IAC5D,YAAY,KAAK;KAChB,OAAO,SAAS;KAChB,OAAO,SAAS,SAAS,SAAS;KAClC,WAAW,SAAS;KACpB,GAAI,QAAQ,QAAQ,IAAI,EAAE,OAAO,QAAQ,QAAQ,EAAE,IAAI,CAAC;IACzD,CAAC;IACD;GACD;GACA,UAAU,KAAK;IAAE,OAAO,SAAS;IAAM;GAAM,CAAC;GAC9C,YAAY,KAAK;IAChB,OAAO,SAAS;IAChB,OAAO,SAAS,SAAS,SAAS;IAClC,WAAW,SAAS;IACpB,GAAI,QAAQ,QAAQ,IAAI,EAAE,OAAO,QAAQ,QAAQ,EAAE,IAAI,CAAC;GACzD,CAAC;GACD;EACD;EAEA,IAAI,SAAS,cAAc,aAAa,SAAS;GAChD,MAAM,QAAQ,MAAM,eAAe;IAClC,OAAO;IACP,QAAQ,UAAU;IAClB,SAAS,kBAAkB,CAAC;IAC5B;IACA;IACA;IACA,UAAU;GACX,CAAC;GACD,cAAc,KAAK;IAAE,OAAO,SAAS;IAAM,GAAG;GAAM,CAAC;GACrD;EACD;EAEA,IAAI,SAAS,cAAc,YAAY;GACtC,MAAM,OAAO,MAAM,QAAQ,KAAK,IAAK,QAA2C,CAAC;GAGjF,MAAM,aACL,MAAM,QAAQ,SAAS,SAAS,IAAK,SAAS,YAAoC,CAAC,GAClF,OAAO,YAAY;GAQrB,KAAK,IAAI,WAAW,GAAG,WAAW,KAAK,QAAQ,YAAY;IAC1D,MAAM,MAAM,KAAK,aAAa,CAAC;IAC/B,KAAK,MAAM,YAAY,WAAW;KACjC,MAAM,SAAS,SAAS,IAAI,SAAS,SAAS;KAC9C,IAAI,CAAC,QAAQ;KAIb,IAAI,OAAO,UAAU,QAAQ;KAC7B,MAAM,UAAU,GAAG,SAAS,KAAK,GAAG,SAAS,IAAI,SAAS;KAC1D,IACC,kBAAkB,SAAS,aAAa,GAAG,KAC3C,kBAAkB,SAAS,cAAc,GAAG,GAC3C;MACD,MAAM,EAAE,QAAQ,cAAc,MAAM,cAAc;OACjD,OAAO;OACP,iBAAiB;OACjB,OAAO,IAAI,SAAS;OACpB,WAAW,SAAS;OACpB;OACA,SAAS;OACT;OACA;OACA;OACA,OAAO;OACP,MAAM;OACN;OACA;OACA;MACD,CAAC;MACD,KAAK,MAAM,WAAW,WACrB,OAAO,KAAK;OAAE,MAAM;OAAS;MAAQ,CAAC;KAExC;KAQA,IAAI,OAAO,UAAU,UAAU,SAAS;MACvC,MAAM,WAAW,IAAI,SAAS;MAC9B,IAAI,QAAQ,QAAQ,GACnB;MAED,IAAI,CAAC,YAAY;OAChB,OAAO,KAAK;QAAE,MAAM;QAAS,SAAS,EAAE,YAAY,SAAS,CAAC;OAAE,CAAC;OACjE;MACD;MACA,MAAM,WAAW,MAAM,eAAe;OACrC;OACA,gBAAgB;OAChB,UAAU;OACV,QAAQ,kBAAkB,QAAQ;OAClC;OACA;OACA,QAAQ;MACT,CAAC;MACD,IAAI,CAAC,SAAS,IAAI;OACjB,OAAO,KAAK;QAAE,MAAM;QAAS,SAAS,EAAE,YAAY,SAAS,IAAI,CAAC;OAAE,CAAC;OACrE;MACD;MACA,IAAI,SAAS,QAAQ,SAAS;KAC/B;IACD;GACD;GAEA,IAAI,OAAO,SAAS,GACnB;GAGD,MAAM,sBAA8C,UAAU,KAAK,QAAQ;IAC1E,OAAO,GAAG;IACV,OAAO,GAAG,SAAS,GAAG;IACtB,WAAW,GAAG;IACd,GAAK,GAAG,UAAsB,EAAE,cAAc,gBAAgB,EAAE,KAAK,KAAA,EAAU,IAAI,CAAC;GACrF,EAAE;GAIF,MAAM,cAAc,IAAI,IACvB,UAAU,QAAQ,OAAO,SAAS,IAAI,GAAG,SAAS,GAAG,UAAU,MAAM,EAAE,KAAK,OAAO,GAAG,IAAI,CAC3F;GACA,MAAM,aACL,YAAY,SAAS,IAClB,OACA,KAAK,KAAK,QACV,OAAO,YAAY,OAAO,QAAQ,GAAG,EAAE,QAAQ,CAAC,SAAS,CAAC,YAAY,IAAI,GAAG,CAAC,CAAC,CAChF;GAEH,UAAU,KAAK;IAAE,OAAO,SAAS;IAAM,OAAO;GAAW,CAAC;GAC1D,YAAY,KAAK;IAChB,OAAO,SAAS;IAChB,OAAO,SAAS,SAAS,SAAS;IAClC,WAAW,SAAS;IACpB,GAAI,QAAQ,QAAQ,IAAI,EAAE,OAAO,QAAQ,QAAQ,EAAE,IAAI,CAAC;IACxD,GAAI,oBAAoB,SAAS,IAAI,EAAE,oBAAoB,IAAI,CAAC;GACjE,CAAC;GACD;EACD;EAEA,IAAI,QAAQ,GAAG,GACd;EAGD,UAAU,KAAK;GAAE,OAAO,SAAS;GAAM;EAAM,CAAC;EAC9C,MAAM,eAAe,gBAAgB,QAAQ;EAC7C,YAAY,KAAK;GAChB,OAAO,SAAS;GAChB,OAAO,SAAS,SAAS,SAAS;GAClC,WAAW,SAAS;GACpB,GAAI,QAAQ,QAAQ,IAAI,EAAE,OAAO,QAAQ,QAAQ,EAAE,IAAI,CAAC;GACxD,GAAI,eAAe,EAAE,aAAa,IAAI,CAAC;EACxC,CAAC;CACF;CAEA,OAAO;EAAE;EAAQ,QAAQ;EAAW;EAAa,SAAS;CAAc;AACzE"}
@@ -19,7 +19,7 @@ import { APIError, ValidationError } from "payload";
19
19
  * Consent fields are captured into `result.consent` (array of proofs, one per visible consent field),
20
20
  * built from the host's sources re-resolved here rather than from anything the form or client carries.
21
21
  */
22
- const validateSubmission = ({ registry, ruleRegistry, consentSources, consentSnapshot, uploadSlug, pollSourceRegistry }) => async ({ data, operation, req }) => {
22
+ const validateSubmission = ({ registry, ruleRegistry, consentSources, consentSnapshot, uploadSlug, pollSourceRegistry, strictUploadOwnership }) => async ({ data, operation, req }) => {
23
23
  if (operation !== "create" || !data) return data;
24
24
  const formId = data.form;
25
25
  if (formId == null) return data;
@@ -90,7 +90,8 @@ const validateSubmission = ({ registry, ruleRegistry, consentSources, consentSna
90
90
  payload: req.payload,
91
91
  formId,
92
92
  uploadSlug,
93
- expectedOwner
93
+ expectedOwner,
94
+ strictUploadOwnership
94
95
  });
95
96
  if (result.errors.length > 0) throw new ValidationError({
96
97
  collection: FORM_SUBMISSIONS_SLUG,
@@ -1 +1 @@
1
- {"version":3,"file":"validateSubmission.js","names":[],"sources":["../../src/submissions/validateSubmission.ts"],"sourcesContent":["import { APIError, type CollectionBeforeValidateHook, ValidationError } from 'payload'\nimport { FORM_SUBMISSIONS_SLUG } from '../collections/formSubmissions'\nimport { FORMS_SLUG } from '../collections/forms'\nimport type { ConsentSnapshotMode } from '../consent/captureConsent'\nimport { resolveConsentEntries } from '../consent/resolveConsentEntries'\nimport type { ConsentSourceEntry, ConsentSourcesResolver } from '../consent/types'\nimport type { FieldTypeRegistry } from '../fields/registry'\nimport { isPollClosed, pollConfigOf } from '../form/pollState'\nimport { applyPollOptions } from '../poll/applyPollOptions'\nimport type { PollOption } from '../poll/definePollOptionSource'\nimport { resolveEffectivePollOptions } from '../poll/effectivePollOptions'\nimport type { PollOptionSourceRegistry } from '../poll/registry'\nimport { IDENTITY_CONTEXT_KEY } from '../spam/constants'\nimport { keys } from '../translations/keys'\nimport { asFieldTranslate, asTranslate } from '../translations/server'\nimport type { ValidationRuleRegistry } from '../validation/registry'\nimport { runSubmission } from './runSubmission'\nimport type { FormFieldInstance, SubmissionValue } from './types'\nimport { POLL_CONTEXT_KEY } from './votedCookie'\n\nexport type ValidateSubmissionArgs = {\n\tregistry: FieldTypeRegistry\n\truleRegistry: ValidationRuleRegistry\n\t/** The host's consent sources resolver (plugin option `consent.sources`); absent when no sources are configured. */\n\tconsentSources?: ConsentSourcesResolver\n\t/** What each consent proof snapshots of the agreed wording (plugin option `consent.snapshot`). */\n\tconsentSnapshot?: ConsentSnapshotMode\n\t/** The plugin-configured uploads collection slug; absent when uploads are disabled. */\n\tuploadSlug?: string\n\t/** Registered poll option sources; a form's configured `optionSource` resolves through this at validation time. */\n\tpollSourceRegistry?: PollOptionSourceRegistry\n}\n\n/**\n * Server-authoritative submission validation. On create it loads the referenced form, re-runs every\n * field's required check, intrinsic validator, and declarative rules through `runSubmission`, threading\n * `req`/`payload` so server-only async rules can hit the DB, and throws a Payload `ValidationError` with\n * per-field paths on any failure. The client is never trusted.\n * Consent fields are captured into `result.consent` (array of proofs, one per visible consent field),\n * built from the host's sources re-resolved here rather than from anything the form or client carries.\n */\nexport const validateSubmission =\n\t({\n\t\tregistry,\n\t\truleRegistry,\n\t\tconsentSources,\n\t\tconsentSnapshot,\n\t\tuploadSlug,\n\t\tpollSourceRegistry,\n\t}: ValidateSubmissionArgs): CollectionBeforeValidateHook =>\n\tasync ({ data, operation, req }) => {\n\t\tif (operation !== 'create' || !data) {\n\t\t\treturn data\n\t\t}\n\t\tconst formId = data.form\n\t\tif (formId == null) {\n\t\t\treturn data\n\t\t}\n\n\t\tconst form = await req.payload.findByID({\n\t\t\tcollection: FORMS_SLUG,\n\t\t\tid: formId as string | number,\n\t\t\tdepth: 0,\n\t\t\tlocale: req.locale,\n\t\t\treq,\n\t\t})\n\n\t\t// Stash whether the form is poll-enabled (its top-level `pollEnabled` flag) so the voted-cookie\n\t\t// afterChange hook can skip a second form fetch on the same request.\n\t\tconst poll = pollConfigOf(form.poll)\n\t\tconst pollEnabled = form.pollEnabled === true\n\t\treq.context[POLL_CONTEXT_KEY] = pollEnabled\n\n\t\t// Form-level lifecycle guard, before any field work: a closed poll accepts no submissions,\n\t\t// regardless of what the client rendered.\n\t\tif (pollEnabled && isPollClosed(poll)) {\n\t\t\tthrow new APIError(asTranslate(req.i18n.t)(keys.pollClosed), 403)\n\t\t}\n\n\t\tlet fields = ((form.fields as FormFieldInstance[] | undefined) ?? []) as FormFieldInstance[]\n\t\tconst incoming = ((data.values as SubmissionValue[] | undefined) ?? []) as SubmissionValue[]\n\t\tconst locale = req.locale ?? 'en'\n\t\tconst t = asFieldTranslate(req.i18n.t)\n\n\t\t// With a resolved choice set for the results field (a poll `optionSource`, or the field type's\n\t\t// own `resolveOptions`) the resolved values are the only accepted answers: options are injected\n\t\t// into the field instance (so the select's membership check and the stored option labels use\n\t\t// them) and membership is also enforced directly, so an empty resolution or a non-select results\n\t\t// field still fails closed. A resolve failure rejects the whole submission rather than skipping\n\t\t// the check. Static authored polls are unaffected: their field validates against its own options\n\t\t// through the normal field pipeline.\n\t\tconst resultsField =\n\t\t\tpollEnabled && typeof poll?.resultsField === 'string' && poll.resultsField.length > 0\n\t\t\t\t? poll.resultsField\n\t\t\t\t: undefined\n\t\tconst resultsInstance = resultsField\n\t\t\t? fields.find((instance) => instance.name === resultsField)\n\t\t\t: undefined\n\t\tconst usesResolver =\n\t\t\t(typeof poll?.optionSource === 'string' && poll.optionSource.length > 0) ||\n\t\t\tBoolean(resultsInstance && registry.get(resultsInstance.blockType)?.resolveOptions)\n\t\tif (resultsField && usesResolver) {\n\t\t\tlet resolved: PollOption[]\n\t\t\ttry {\n\t\t\t\tresolved = await resolveEffectivePollOptions({\n\t\t\t\t\tpayload: req.payload,\n\t\t\t\t\treq,\n\t\t\t\t\tform,\n\t\t\t\t\tsources: pollSourceRegistry ?? new Map(),\n\t\t\t\t\tfieldTypes: registry,\n\t\t\t\t})\n\t\t\t} catch {\n\t\t\t\tthrow new APIError(asTranslate(req.i18n.t)(keys.pollOptionsUnavailable), 503)\n\t\t\t}\n\t\t\tfields = applyPollOptions(fields, resultsField, resolved)\n\t\t\tconst answer = incoming.find((entry) => entry.field === resultsField)?.value\n\t\t\tconst isAnswered = answer != null && answer !== ''\n\t\t\tif (isAnswered && !resolved.some((option) => option.value === answer)) {\n\t\t\t\tthrow new ValidationError(\n\t\t\t\t\t{\n\t\t\t\t\t\tcollection: FORM_SUBMISSIONS_SLUG,\n\t\t\t\t\t\terrors: [\n\t\t\t\t\t\t\t{ path: resultsField, message: asTranslate(req.i18n.t)(keys.validationSelect) },\n\t\t\t\t\t\t],\n\t\t\t\t\t},\n\t\t\t\t\treq.t\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\tconst expectedOwner =\n\t\t\ttypeof req.context?.[IDENTITY_CONTEXT_KEY] === 'string'\n\t\t\t\t? (req.context[IDENTITY_CONTEXT_KEY] as string)\n\t\t\t\t: undefined\n\n\t\t// Every consent proof is built from the source as it reads right now, not from anything the\n\t\t// form or the client carries, so a resolver failure rejects the submission rather than\n\t\t// recording an agreement to a statement the server cannot vouch for.\n\t\tlet consentEntries: ConsentSourceEntry[] = []\n\t\tif (consentSources && fields.some((field) => field.blockType === 'consent')) {\n\t\t\ttry {\n\t\t\t\tconsentEntries = await resolveConsentEntries({\n\t\t\t\t\tpayload: req.payload,\n\t\t\t\t\treq,\n\t\t\t\t\tform,\n\t\t\t\t\tsources: consentSources,\n\t\t\t\t})\n\t\t\t} catch {\n\t\t\t\tthrow new APIError(asTranslate(req.i18n.t)(keys.consentSourcesUnavailable), 503)\n\t\t\t}\n\t\t}\n\n\t\tconst result = await runSubmission({\n\t\t\tfields,\n\t\t\tvalues: incoming,\n\t\t\tregistry,\n\t\t\truleRegistry,\n\t\t\tconsentEntries,\n\t\t\tconsentSnapshot,\n\t\t\tlocale,\n\t\t\tt,\n\t\t\toperation: 'create',\n\t\t\treq,\n\t\t\tpayload: req.payload,\n\t\t\tformId: formId as number | string,\n\t\t\tuploadSlug,\n\t\t\texpectedOwner,\n\t\t})\n\n\t\tif (result.errors.length > 0) {\n\t\t\tthrow new ValidationError({ collection: FORM_SUBMISSIONS_SLUG, errors: result.errors }, req.t)\n\t\t}\n\n\t\tdata.values = result.values\n\t\tdata.descriptors = result.descriptors\n\t\tdata.consent = result.consent.length > 0 ? result.consent : undefined\n\t\tdata.locale = locale\n\t\t// Unauthenticated submits are always 'complete'. A client-supplied 'partial' would\n\t\t// silently skip all post-submit actions and events. Authenticated callers (e.g. an\n\t\t// admin draft-save flow) may set status themselves.\n\t\tif (!req.user) {\n\t\t\tdata.status = 'complete'\n\t\t}\n\t\treturn data\n\t}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAyCA,MAAa,sBACX,EACA,UACA,cACA,gBACA,iBACA,YACA,yBAED,OAAO,EAAE,MAAM,WAAW,UAAU;CACnC,IAAI,cAAc,YAAY,CAAC,MAC9B,OAAO;CAER,MAAM,SAAS,KAAK;CACpB,IAAI,UAAU,MACb,OAAO;CAGR,MAAM,OAAO,MAAM,IAAI,QAAQ,SAAS;EACvC,YAAY;EACZ,IAAI;EACJ,OAAO;EACP,QAAQ,IAAI;EACZ;CACD,CAAC;CAID,MAAM,OAAO,aAAa,KAAK,IAAI;CACnC,MAAM,cAAc,KAAK,gBAAgB;CACzC,IAAI,QAAQ,oBAAoB;CAIhC,IAAI,eAAe,aAAa,IAAI,GACnC,MAAM,IAAI,SAAS,YAAY,IAAI,KAAK,CAAC,EAAE,KAAK,UAAU,GAAG,GAAG;CAGjE,IAAI,SAAW,KAAK,UAA8C,CAAC;CACnE,MAAM,WAAa,KAAK,UAA4C,CAAC;CACrE,MAAM,SAAS,IAAI,UAAU;CAC7B,MAAM,IAAI,iBAAiB,IAAI,KAAK,CAAC;CASrC,MAAM,eACL,eAAe,OAAO,MAAM,iBAAiB,YAAY,KAAK,aAAa,SAAS,IACjF,KAAK,eACL,KAAA;CACJ,MAAM,kBAAkB,eACrB,OAAO,MAAM,aAAa,SAAS,SAAS,YAAY,IACxD,KAAA;CACH,MAAM,eACJ,OAAO,MAAM,iBAAiB,YAAY,KAAK,aAAa,SAAS,KACtE,QAAQ,mBAAmB,SAAS,IAAI,gBAAgB,SAAS,GAAG,cAAc;CACnF,IAAI,gBAAgB,cAAc;EACjC,IAAI;EACJ,IAAI;GACH,WAAW,MAAM,4BAA4B;IAC5C,SAAS,IAAI;IACb;IACA;IACA,SAAS,sCAAsB,IAAI,IAAI;IACvC,YAAY;GACb,CAAC;EACF,QAAQ;GACP,MAAM,IAAI,SAAS,YAAY,IAAI,KAAK,CAAC,EAAE,KAAK,sBAAsB,GAAG,GAAG;EAC7E;EACA,SAAS,iBAAiB,QAAQ,cAAc,QAAQ;EACxD,MAAM,SAAS,SAAS,MAAM,UAAU,MAAM,UAAU,YAAY,GAAG;EAEvE,IADmB,UAAU,QAAQ,WAAW,MAC9B,CAAC,SAAS,MAAM,WAAW,OAAO,UAAU,MAAM,GACnE,MAAM,IAAI,gBACT;GACC,YAAY;GACZ,QAAQ,CACP;IAAE,MAAM;IAAc,SAAS,YAAY,IAAI,KAAK,CAAC,EAAE,KAAK,gBAAgB;GAAE,CAC/E;EACD,GACA,IAAI,CACL;CAEF;CACA,MAAM,gBACL,OAAO,IAAI,UAAA,+BAAoC,WAC3C,IAAI,QAAQ,wBACb,KAAA;CAKJ,IAAI,iBAAuC,CAAC;CAC5C,IAAI,kBAAkB,OAAO,MAAM,UAAU,MAAM,cAAc,SAAS,GACzE,IAAI;EACH,iBAAiB,MAAM,sBAAsB;GAC5C,SAAS,IAAI;GACb;GACA;GACA,SAAS;EACV,CAAC;CACF,QAAQ;EACP,MAAM,IAAI,SAAS,YAAY,IAAI,KAAK,CAAC,EAAE,KAAK,yBAAyB,GAAG,GAAG;CAChF;CAGD,MAAM,SAAS,MAAM,cAAc;EAClC;EACA,QAAQ;EACR;EACA;EACA;EACA;EACA;EACA;EACA,WAAW;EACX;EACA,SAAS,IAAI;EACL;EACR;EACA;CACD,CAAC;CAED,IAAI,OAAO,OAAO,SAAS,GAC1B,MAAM,IAAI,gBAAgB;EAAE,YAAY;EAAuB,QAAQ,OAAO;CAAO,GAAG,IAAI,CAAC;CAG9F,KAAK,SAAS,OAAO;CACrB,KAAK,cAAc,OAAO;CAC1B,KAAK,UAAU,OAAO,QAAQ,SAAS,IAAI,OAAO,UAAU,KAAA;CAC5D,KAAK,SAAS;CAId,IAAI,CAAC,IAAI,MACR,KAAK,SAAS;CAEf,OAAO;AACR"}
1
+ {"version":3,"file":"validateSubmission.js","names":[],"sources":["../../src/submissions/validateSubmission.ts"],"sourcesContent":["import { APIError, type CollectionBeforeValidateHook, ValidationError } from 'payload'\nimport { FORM_SUBMISSIONS_SLUG } from '../collections/formSubmissions'\nimport { FORMS_SLUG } from '../collections/forms'\nimport type { ConsentSnapshotMode } from '../consent/captureConsent'\nimport { resolveConsentEntries } from '../consent/resolveConsentEntries'\nimport type { ConsentSourceEntry, ConsentSourcesResolver } from '../consent/types'\nimport type { FieldTypeRegistry } from '../fields/registry'\nimport { isPollClosed, pollConfigOf } from '../form/pollState'\nimport { applyPollOptions } from '../poll/applyPollOptions'\nimport type { PollOption } from '../poll/definePollOptionSource'\nimport { resolveEffectivePollOptions } from '../poll/effectivePollOptions'\nimport type { PollOptionSourceRegistry } from '../poll/registry'\nimport { IDENTITY_CONTEXT_KEY } from '../spam/constants'\nimport { keys } from '../translations/keys'\nimport { asFieldTranslate, asTranslate } from '../translations/server'\nimport type { ValidationRuleRegistry } from '../validation/registry'\nimport { runSubmission } from './runSubmission'\nimport type { FormFieldInstance, SubmissionValue } from './types'\nimport { POLL_CONTEXT_KEY } from './votedCookie'\n\nexport type ValidateSubmissionArgs = {\n\tregistry: FieldTypeRegistry\n\truleRegistry: ValidationRuleRegistry\n\t/** The host's consent sources resolver (plugin option `consent.sources`); absent when no sources are configured. */\n\tconsentSources?: ConsentSourcesResolver\n\t/** What each consent proof snapshots of the agreed wording (plugin option `consent.snapshot`). */\n\tconsentSnapshot?: ConsentSnapshotMode\n\t/** The plugin-configured uploads collection slug; absent when uploads are disabled. */\n\tuploadSlug?: string\n\t/** Registered poll option sources; a form's configured `optionSource` resolves through this at validation time. */\n\tpollSourceRegistry?: PollOptionSourceRegistry\n\t/** Plugin `spam.uploadOwnership: 'strict'`: reject an owned upload when the submitter is unidentifiable. */\n\tstrictUploadOwnership?: boolean\n}\n\n/**\n * Server-authoritative submission validation. On create it loads the referenced form, re-runs every\n * field's required check, intrinsic validator, and declarative rules through `runSubmission`, threading\n * `req`/`payload` so server-only async rules can hit the DB, and throws a Payload `ValidationError` with\n * per-field paths on any failure. The client is never trusted.\n * Consent fields are captured into `result.consent` (array of proofs, one per visible consent field),\n * built from the host's sources re-resolved here rather than from anything the form or client carries.\n */\nexport const validateSubmission =\n\t({\n\t\tregistry,\n\t\truleRegistry,\n\t\tconsentSources,\n\t\tconsentSnapshot,\n\t\tuploadSlug,\n\t\tpollSourceRegistry,\n\t\tstrictUploadOwnership,\n\t}: ValidateSubmissionArgs): CollectionBeforeValidateHook =>\n\tasync ({ data, operation, req }) => {\n\t\tif (operation !== 'create' || !data) {\n\t\t\treturn data\n\t\t}\n\t\tconst formId = data.form\n\t\tif (formId == null) {\n\t\t\treturn data\n\t\t}\n\n\t\tconst form = await req.payload.findByID({\n\t\t\tcollection: FORMS_SLUG,\n\t\t\tid: formId as string | number,\n\t\t\tdepth: 0,\n\t\t\tlocale: req.locale,\n\t\t\treq,\n\t\t})\n\n\t\t// Stash whether the form is poll-enabled (its top-level `pollEnabled` flag) so the voted-cookie\n\t\t// afterChange hook can skip a second form fetch on the same request.\n\t\tconst poll = pollConfigOf(form.poll)\n\t\tconst pollEnabled = form.pollEnabled === true\n\t\treq.context[POLL_CONTEXT_KEY] = pollEnabled\n\n\t\t// Form-level lifecycle guard, before any field work: a closed poll accepts no submissions,\n\t\t// regardless of what the client rendered.\n\t\tif (pollEnabled && isPollClosed(poll)) {\n\t\t\tthrow new APIError(asTranslate(req.i18n.t)(keys.pollClosed), 403)\n\t\t}\n\n\t\tlet fields = ((form.fields as FormFieldInstance[] | undefined) ?? []) as FormFieldInstance[]\n\t\tconst incoming = ((data.values as SubmissionValue[] | undefined) ?? []) as SubmissionValue[]\n\t\tconst locale = req.locale ?? 'en'\n\t\tconst t = asFieldTranslate(req.i18n.t)\n\n\t\t// With a resolved choice set for the results field (a poll `optionSource`, or the field type's\n\t\t// own `resolveOptions`) the resolved values are the only accepted answers: options are injected\n\t\t// into the field instance (so the select's membership check and the stored option labels use\n\t\t// them) and membership is also enforced directly, so an empty resolution or a non-select results\n\t\t// field still fails closed. A resolve failure rejects the whole submission rather than skipping\n\t\t// the check. Static authored polls are unaffected: their field validates against its own options\n\t\t// through the normal field pipeline.\n\t\tconst resultsField =\n\t\t\tpollEnabled && typeof poll?.resultsField === 'string' && poll.resultsField.length > 0\n\t\t\t\t? poll.resultsField\n\t\t\t\t: undefined\n\t\tconst resultsInstance = resultsField\n\t\t\t? fields.find((instance) => instance.name === resultsField)\n\t\t\t: undefined\n\t\tconst usesResolver =\n\t\t\t(typeof poll?.optionSource === 'string' && poll.optionSource.length > 0) ||\n\t\t\tBoolean(resultsInstance && registry.get(resultsInstance.blockType)?.resolveOptions)\n\t\tif (resultsField && usesResolver) {\n\t\t\tlet resolved: PollOption[]\n\t\t\ttry {\n\t\t\t\tresolved = await resolveEffectivePollOptions({\n\t\t\t\t\tpayload: req.payload,\n\t\t\t\t\treq,\n\t\t\t\t\tform,\n\t\t\t\t\tsources: pollSourceRegistry ?? new Map(),\n\t\t\t\t\tfieldTypes: registry,\n\t\t\t\t})\n\t\t\t} catch {\n\t\t\t\tthrow new APIError(asTranslate(req.i18n.t)(keys.pollOptionsUnavailable), 503)\n\t\t\t}\n\t\t\tfields = applyPollOptions(fields, resultsField, resolved)\n\t\t\tconst answer = incoming.find((entry) => entry.field === resultsField)?.value\n\t\t\tconst isAnswered = answer != null && answer !== ''\n\t\t\tif (isAnswered && !resolved.some((option) => option.value === answer)) {\n\t\t\t\tthrow new ValidationError(\n\t\t\t\t\t{\n\t\t\t\t\t\tcollection: FORM_SUBMISSIONS_SLUG,\n\t\t\t\t\t\terrors: [\n\t\t\t\t\t\t\t{ path: resultsField, message: asTranslate(req.i18n.t)(keys.validationSelect) },\n\t\t\t\t\t\t],\n\t\t\t\t\t},\n\t\t\t\t\treq.t\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\tconst expectedOwner =\n\t\t\ttypeof req.context?.[IDENTITY_CONTEXT_KEY] === 'string'\n\t\t\t\t? (req.context[IDENTITY_CONTEXT_KEY] as string)\n\t\t\t\t: undefined\n\n\t\t// Every consent proof is built from the source as it reads right now, not from anything the\n\t\t// form or the client carries, so a resolver failure rejects the submission rather than\n\t\t// recording an agreement to a statement the server cannot vouch for.\n\t\tlet consentEntries: ConsentSourceEntry[] = []\n\t\tif (consentSources && fields.some((field) => field.blockType === 'consent')) {\n\t\t\ttry {\n\t\t\t\tconsentEntries = await resolveConsentEntries({\n\t\t\t\t\tpayload: req.payload,\n\t\t\t\t\treq,\n\t\t\t\t\tform,\n\t\t\t\t\tsources: consentSources,\n\t\t\t\t})\n\t\t\t} catch {\n\t\t\t\tthrow new APIError(asTranslate(req.i18n.t)(keys.consentSourcesUnavailable), 503)\n\t\t\t}\n\t\t}\n\n\t\tconst result = await runSubmission({\n\t\t\tfields,\n\t\t\tvalues: incoming,\n\t\t\tregistry,\n\t\t\truleRegistry,\n\t\t\tconsentEntries,\n\t\t\tconsentSnapshot,\n\t\t\tlocale,\n\t\t\tt,\n\t\t\toperation: 'create',\n\t\t\treq,\n\t\t\tpayload: req.payload,\n\t\t\tformId: formId as number | string,\n\t\t\tuploadSlug,\n\t\t\texpectedOwner,\n\t\t\tstrictUploadOwnership,\n\t\t})\n\n\t\tif (result.errors.length > 0) {\n\t\t\tthrow new ValidationError({ collection: FORM_SUBMISSIONS_SLUG, errors: result.errors }, req.t)\n\t\t}\n\n\t\tdata.values = result.values\n\t\tdata.descriptors = result.descriptors\n\t\tdata.consent = result.consent.length > 0 ? result.consent : undefined\n\t\tdata.locale = locale\n\t\t// Unauthenticated submits are always 'complete'. A client-supplied 'partial' would\n\t\t// silently skip all post-submit actions and events. Authenticated callers (e.g. an\n\t\t// admin draft-save flow) may set status themselves.\n\t\tif (!req.user) {\n\t\t\tdata.status = 'complete'\n\t\t}\n\t\treturn data\n\t}\n"],"mappings":"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"}
@@ -134,6 +134,8 @@ const de = {
134
134
  [keys.recipientsGroupFields]: "Formularfelder",
135
135
  [keys.validationRecipientInvalid]: "Gib eine gültige E-Mail-Adresse ein.",
136
136
  [keys.validationRecipientUnknownField]: "Verweist auf ein nicht mehr vorhandenes Feld.",
137
+ [keys.validationRecipientNotAllowed]: "Dieser Empfänger steht nicht auf der zulässigen Liste.",
138
+ [keys.validationRecipientOptionsUnavailable]: "Empfängeroptionen sind derzeit nicht verfügbar. Bitte versuche es später erneut.",
137
139
  [keys.validationFromUnknown]: "Wähle eine der konfigurierten Absenderadressen",
138
140
  [keys.validationFromUnavailable]: "Absenderadressen sind derzeit nicht verfügbar. Bitte versuche es später erneut.",
139
141
  [keys.actionConfigUrl]: "URL",
@@ -194,6 +196,7 @@ const de = {
194
196
  [keys.endpointOptionsError]: "Optionen konnten nicht geladen werden.",
195
197
  [keys.pollOptionsUnavailable]: "Umfrageoptionen sind derzeit nicht verfügbar. Bitte versuche es später erneut.",
196
198
  [keys.pollFinalResult]: "Endergebnis",
199
+ [keys.pollResultsError]: "Ergebnisse konnten nicht geladen werden.",
197
200
  [keys.resultsWinner]: "Gewinner",
198
201
  [keys.validationFileMissing]: "Datei hochladen",
199
202
  [keys.validationFileMimeType]: "Dateityp nicht erlaubt",
@@ -206,6 +209,10 @@ const de = {
206
209
  [keys.fileUploadMisconfigured]: "Datei-Uploads sind für dieses Formular nicht konfiguriert",
207
210
  [keys.fileHintAccepted]: "Akzeptiert: {types}",
208
211
  [keys.fileHintMaxSize]: "Max. Größe: {max}",
212
+ [keys.fileUploaded]: "Hochgeladene Datei",
213
+ [keys.fileUploading]: "Wird hochgeladen",
214
+ [keys.fileUploadFailed]: "Upload fehlgeschlagen",
215
+ [keys.fileRemove]: "Entfernen",
209
216
  [keys.spamRateLimited]: "Du hast zu viele Anfragen gesendet. Bitte versuche es später erneut.",
210
217
  [keys.spamRejected]: "Deine Übermittlung konnte nicht verarbeitet werden.",
211
218
  [keys.spamCaptchaFailed]: "Captcha-Überprüfung fehlgeschlagen. Bitte versuche es erneut.",
@@ -278,6 +285,9 @@ const de = {
278
285
  [keys.formSubmit]: "Absenden",
279
286
  [keys.formMultistep]: "Mehrstufig",
280
287
  [keys.formPollEnabled]: "Umfrage",
288
+ [keys.formClose]: "Schließen",
289
+ [keys.formSuccess]: "Vielen Dank.",
290
+ [keys.formSubmitFailed]: "Übermittlung fehlgeschlagen",
281
291
  [keys.cellStepCountOne]: "{{count}} Schritt",
282
292
  [keys.cellStepCountOther]: "{{count}} Schritte",
283
293
  [keys.cellFieldCountOne]: "{{count}} Feld",
@@ -1 +1 @@
1
- {"version":3,"file":"de.js","names":[],"sources":["../../src/translations/de.ts"],"sourcesContent":["import { keys, type TranslationKey } from './keys'\n\n/**\n * German values, keyed by the typed constants in `keys.ts` so the two stay in\n * lockstep. The `Record<TranslationKey, string>` annotation makes a missing or\n * unknown key a type error. `translations/index.ts` nests these for Payload.\n */\nexport const de: Record<TranslationKey, string> = {\n\t[keys.fieldTitle]: 'Titel',\n\t[keys.fieldTypeText]: 'Text',\n\t[keys.fieldTypeTextarea]: 'Textarea',\n\t[keys.fieldTypeEmail]: 'E-Mail',\n\t[keys.fieldTypeNumber]: 'Zahl',\n\t[keys.fieldTypeSelect]: 'Auswahl',\n\t[keys.fieldTypeCountry]: 'Land',\n\t[keys.fieldTypeState]: 'Bundesstaat',\n\t[keys.fieldTypeCheckbox]: 'Checkbox',\n\t[keys.fieldTypeDate]: 'Datum',\n\t[keys.configOptions]: 'Optionen',\n\t[keys.configOption]: 'Option',\n\t[keys.configOptionLabel]: 'Bezeichnung',\n\t[keys.configOptionValue]: 'Wert',\n\t[keys.validationRequired]: 'Dieses Feld ist erforderlich',\n\t[keys.validationEmail]: 'Gib eine gültige E-Mail-Adresse ein',\n\t[keys.validationNumber]: 'Gib eine gültige Zahl ein',\n\t[keys.validationDate]: 'Gib ein gültiges Datum ein',\n\t[keys.validationSelect]: 'Wähle eine gültige Option',\n\t[keys.validationCountry]: 'Wähle ein gültiges Land',\n\t[keys.validationState]: 'Wähle einen gültigen Bundesstaat',\n\t[keys.validationRegexPattern]: 'Gib einen gültigen regulären Ausdruck ein',\n\t[keys.validationRegexFlags]:\n\t\t'Gib gültige Flags für reguläre Ausdrücke ein, zum Beispiel i oder gi',\n\t[keys.validationEmailFieldUnknown]: 'Wähle ein bestehendes E-Mail-Feld dieses Formulars',\n\t[keys.validationResultsFieldUnknown]: 'Wähle ein geeignetes Auswahlfeld dieses Formulars',\n\t[keys.formatYes]: 'Ja',\n\t[keys.formatNo]: 'Nein',\n\t[keys.configName]: 'Name',\n\t[keys.configLabel]: 'Bezeichnung',\n\t[keys.configRequired]: 'Erforderlich',\n\t[keys.configWidth]: 'Breite',\n\t[keys.configPlaceholder]: 'Platzhalter',\n\t[keys.configDescription]: 'Beschreibung',\n\t[keys.configVisibleWhen]: 'Dieses Feld anzeigen, wenn',\n\t[keys.configValidateWhen]: 'Dieses Feld nur validieren, wenn',\n\t[keys.submissionAnswers]: 'Antworten',\n\t[keys.submissionNoAnswers]: 'Keine Antworten',\n\t[keys.ruleMinLength]: 'Minimale Länge',\n\t[keys.ruleMaxLength]: 'Maximale Länge',\n\t[keys.ruleMin]: 'Minimum',\n\t[keys.ruleMax]: 'Maximum',\n\t[keys.ruleMinDate]: 'Frühestes Datum',\n\t[keys.ruleMaxDate]: 'Spätestes Datum',\n\t[keys.rulePattern]: 'Muster',\n\t[keys.ruleEmail]: 'E-Mail',\n\t[keys.ruleUrl]: 'URL',\n\t[keys.ruleOneOf]: 'Wert aus Liste',\n\t[keys.ruleMatchesField]: 'Stimmt mit Feld überein',\n\t[keys.ruleNotAlreadySubmitted]: 'Noch nicht übermittelt',\n\t[keys.ruleMinLengthMessage]: 'Muss mindestens {min} Zeichen lang sein',\n\t[keys.ruleMaxLengthMessage]: 'Darf höchstens {max} Zeichen lang sein',\n\t[keys.ruleMinMessage]: 'Muss mindestens {min} betragen',\n\t[keys.ruleMaxMessage]: 'Darf höchstens {max} betragen',\n\t[keys.ruleMinDateMessage]: 'Muss am oder nach dem {min} liegen',\n\t[keys.ruleMaxDateMessage]: 'Muss am oder vor dem {max} liegen',\n\t[keys.rulePatternMessage]: 'Ungültiges Format',\n\t[keys.ruleEmailMessage]: 'Gib eine gültige E-Mail-Adresse ein',\n\t[keys.ruleUrlMessage]: 'Gib eine gültige URL ein',\n\t[keys.ruleOneOfMessage]: 'Wähle einen zulässigen Wert',\n\t[keys.ruleMatchesFieldMessage]: 'Stimmt nicht überein',\n\t[keys.ruleNotAlreadySubmittedMessage]: 'Dieser Wert wurde bereits übermittelt',\n\t[keys.ruleMinLengthDescription]:\n\t\t'Schlägt fehl, wenn der eingegebene Text kürzer als die Mindestanzahl an Zeichen ist.',\n\t[keys.ruleMaxLengthDescription]:\n\t\t'Schlägt fehl, wenn der eingegebene Text länger als die maximale Anzahl an Zeichen ist.',\n\t[keys.ruleMinDescription]: 'Schlägt fehl, wenn die eingegebene Zahl unter dem Minimum liegt.',\n\t[keys.ruleMaxDescription]: 'Schlägt fehl, wenn die eingegebene Zahl über dem Maximum liegt.',\n\t[keys.ruleMinDateDescription]:\n\t\t'Schlägt fehl, wenn das gewählte Datum vor dem frühesten Datum liegt.',\n\t[keys.ruleMaxDateDescription]:\n\t\t'Schlägt fehl, wenn das gewählte Datum nach dem spätesten Datum liegt.',\n\t[keys.rulePatternDescription]:\n\t\t'Schlägt fehl, wenn der eingegebene Text nicht zum regulären Ausdruck passt.',\n\t[keys.ruleEmailDescription]:\n\t\t'Schlägt fehl, wenn der eingegebene Wert keine gültige E-Mail-Adresse ist.',\n\t[keys.ruleUrlDescription]:\n\t\t'Schlägt fehl, wenn der eingegebene Wert keine gültige http- oder https-URL ist.',\n\t[keys.ruleOneOfDescription]:\n\t\t'Schlägt fehl, wenn der eingegebene Wert keiner der von dir angegebenen zulässigen Werte ist.',\n\t[keys.ruleMatchesFieldDescription]:\n\t\t'Schlägt fehl, wenn der Wert dieses Feldes nicht dem gewählten Feld entspricht. Nutze es für E-Mail- oder Passwort-Bestätigung.',\n\t[keys.ruleNotAlreadySubmittedDescription]:\n\t\t'Schlägt fehl, wenn genau dieser Wert bereits an dieses Formular übermittelt wurde (serverseitig geprüft).',\n\t[keys.ruleFieldTargetInvalid]:\n\t\t'Das ausgewählte Feld existiert nicht mehr. Wähle ein gültiges Feld.',\n\t[keys.ruleParamMin]: 'Minimum',\n\t[keys.ruleParamMax]: 'Maximum',\n\t[keys.ruleParamMinDate]: 'Frühestes Datum (JJJJ-MM-TT)',\n\t[keys.ruleParamMaxDate]: 'Spätestes Datum (JJJJ-MM-TT)',\n\t[keys.ruleParamPattern]: 'Muster',\n\t[keys.ruleParamFlags]: 'Flags',\n\t[keys.ruleParamValues]: 'Zulässige Werte',\n\t[keys.ruleParamField]: 'Feldname',\n\t[keys.validationsLabel]: 'Validierungsregeln',\n\t[keys.validationMessageLabel]: 'Benutzerdefinierte Nachricht',\n\t[keys.conditionAddCondition]: 'Bedingung hinzufügen',\n\t[keys.conditionAddOr]: '\"Oder\"-Gruppe hinzufügen',\n\t[keys.conditionAnd]: 'Und',\n\t[keys.conditionOr]: 'Oder',\n\t[keys.conditionRemove]: 'Entfernen',\n\t[keys.conditionNoFields]:\n\t\t'Füge diesem Formular benannte Felder hinzu, um eine Bedingung zu erstellen.',\n\t[keys.conditionEmpty]: 'Keine Bedingungen. Dieses Feld wird immer angezeigt.',\n\t[keys.conditionSelectField]: 'Feld auswählen',\n\t[keys.conditionTrue]: 'Wahr',\n\t[keys.conditionFalse]: 'Falsch',\n\t[keys.configHidden]: 'Ausgeblendet (wird erfasst, aber nicht angezeigt)',\n\t[keys.tabFields]: 'Felder',\n\t[keys.tabFlow]: 'Ablauf',\n\t[keys.tabActions]: 'Aktionen',\n\t[keys.tabField]: 'Feld',\n\t[keys.tabValidation]: 'Validierung',\n\t[keys.tabAdvanced]: 'Erweitert',\n\t[keys.fieldTypeCalculation]: 'Berechnung',\n\t[keys.configExpression]: 'Ausdruck (JSON)',\n\t[keys.configExpressionDescription]:\n\t\t'Ausdrucksbaum aus Knoten {\"type\":\"lit\"|\"ref\"|\"op\"|\"neg\"|\"fn\"|\"weight\"}. Beispiel: {\"type\":\"op\",\"op\":\"+\",\"left\":{\"type\":\"ref\",\"field\":\"qty\"},\"right\":{\"type\":\"lit\",\"value\":10}}',\n\t[keys.configCalcDisplay]: 'Berechneten Wert anzeigen',\n\t[keys.validationCalcExpressionInvalid]: 'Gib einen gültigen Berechnungsausdruck ein',\n\t[keys.presentationPage]: 'Seite',\n\t[keys.presentationModal]: 'Modal',\n\t[keys.presentationDrawer]: 'Seitenpanel',\n\t[keys.presentationInline]: 'Inline',\n\t[keys.actionEmailTeam]: 'E-Mail ans Team',\n\t[keys.actionConfirmation]: 'Bestätigungs-E-Mail',\n\t[keys.actionSignedWebhook]: 'Signierter Webhook',\n\t[keys.actionConfigTo]: 'An',\n\t[keys.actionConfigSubject]: 'Betreff',\n\t[keys.actionConfigBody]: 'Nachrichtentext',\n\t[keys.actionConfigBodyDescription]:\n\t\t'Unterstützt {{ fieldName|fallback }}-Platzhalter, {{*}} für alle Antworten als Zeilen und {{*:table}} für alle Antworten als Tabelle.',\n\t[keys.actionConfigToField]: 'Name des E-Mail-Feldes',\n\t[keys.actionConfigToFieldDescription]:\n\t\t'Das E-Mail-Feld dieses Formulars, an das die Bestätigung gesendet wird.',\n\t[keys.actionConfigFrom]: 'Von',\n\t[keys.actionConfigFromDescription]:\n\t\t'Absenderadresse für diese Aktion. Leer lassen, um den Standard des E-Mail-Adapters zu verwenden.',\n\t[keys.actionConfigCc]: 'CC',\n\t[keys.actionConfigBcc]: 'BCC',\n\t[keys.actionConfigReplyTo]: 'Antwort an',\n\t[keys.recipientsGroupDepartments]: 'Abteilungen',\n\t[keys.recipientsGroupFields]: 'Formularfelder',\n\t[keys.validationRecipientInvalid]: 'Gib eine gültige E-Mail-Adresse ein.',\n\t[keys.validationRecipientUnknownField]: 'Verweist auf ein nicht mehr vorhandenes Feld.',\n\t[keys.validationFromUnknown]: 'Wähle eine der konfigurierten Absenderadressen',\n\t[keys.validationFromUnavailable]:\n\t\t'Absenderadressen sind derzeit nicht verfügbar. Bitte versuche es später erneut.',\n\t[keys.actionConfigUrl]: 'URL',\n\t[keys.actionConfigUrlDescription]:\n\t\t'Der Endpunkt, der für jede Übermittlung einen signierten JSON-POST erhält.',\n\t[keys.actionConfigSecret]: 'Geheimer Schlüssel',\n\t[keys.actionConfigSecretDescription]:\n\t\t'HMAC-Schlüssel für den X-Form-Signature-Header, der mit dem Empfänger geteilt wird.',\n\t[keys.validationUrlInvalid]: 'Gib eine gültige http- oder https-URL ein',\n\t[keys.configActions]: 'Aktionen',\n\t[keys.fieldTypeConsent]: 'Einwilligung',\n\t[keys.consentConfigSource]: 'Quelle',\n\t[keys.consentConfigSourceDescription]:\n\t\t'Die Erklärung, der Besucher zustimmen. Wortlaut und Rechtsseite gehören zur Quelle: eine Änderung dort gilt für jedes Formular, das sie nutzt.',\n\t[keys.consentSourcesField]: 'Einwilligungsquellen',\n\t[keys.consentSourcesFieldDescription]:\n\t\t'Erklärungen, die Formulare in Einwilligungsfeldern verwenden können',\n\t[keys.consentSourceSingular]: 'Einwilligungsquelle',\n\t[keys.consentSourcePlural]: 'Einwilligungsquellen',\n\t[keys.consentSourceLabel]: 'Name',\n\t[keys.consentSourceStatement]: 'Erklärung',\n\t[keys.consentSourcePage]: 'Erklärungsquelle',\n\t[keys.consentSourcePageDescription]:\n\t\t'Muss gesetzt sein, wenn Formulareinsendungen einen Verweis auf deine Richtlinie speichern sollen.',\n\t[keys.consentSourcesUnavailable]:\n\t\t'Einwilligungsquellen sind nicht verfügbar. Versuche es gleich noch einmal.',\n\t[keys.resultsResponses]: 'Antworten',\n\t[keys.resultsNoResponses]: 'Noch keine Antworten',\n\t[keys.resultsTruncated]: 'Zeigt eine Stichprobe der Antworten',\n\t[keys.pollGroup]: 'Umfrage',\n\t[keys.pollResultsField]: 'Abstimmungsfeld',\n\t[keys.pollResultsFieldDescription]:\n\t\t'Das Auswahlfeld, dessen Antworten als Stimmen gezählt werden. Wird automatisch gewählt, wenn dein Formular genau ein Auswahlfeld hat. Verwende ein Auswahlfeld, niemals ein Freitext- oder personenbezogenes Feld.',\n\t[keys.pollVoteFieldChoose]: 'Wähle das Feld, dessen Antworten als Stimmen zählen.',\n\t[keys.pollVoteFieldMissing]: 'Füge ein Auswahlfeld als Abstimmungsfrage hinzu.',\n\t[keys.pollResultsVisibility]: 'Sichtbarkeit der Ergebnisse',\n\t[keys.pollVisibilityAfterVote]: 'Nach der Abstimmung',\n\t[keys.pollVisibilityAfterClose]: 'Nach Ende der Umfrage',\n\t[keys.pollClosesAt]: 'Endet am',\n\t[keys.pollClosed]: 'Diese Umfrage ist beendet.',\n\t[keys.pollResultsAfterClose]: 'Die Ergebnisse werden nach Ende der Umfrage angezeigt.',\n\t[keys.pollOptionSource]: 'Optionsquelle',\n\t[keys.pollOptionSourceDescription]:\n\t\t'Befüllt die Auswahlmöglichkeiten des Ergebnisfeldes mit App-Daten anstelle manuell erstellter Optionen.',\n\t[keys.pollSourceConfig]: 'Quelleinstellungen',\n\t[keys.pollOutcome]: 'Ergebnis',\n\t[keys.pollType]: 'Ergebnistyp',\n\t[keys.pollTypeDescription]: 'Wie die Gewinneroption bestimmt wird, sobald die Umfrage endet.',\n\t[keys.pollTypeManual]: 'Gewinner manuell festlegen',\n\t[keys.pollTypeMostVoted]: 'Meistgewählte Option gewinnt',\n\t[keys.pollTypeSource]: 'Gewinner aus der Optionsquelle',\n\t[keys.pollCloseButton]: 'Umfrage jetzt beenden',\n\t[keys.pollReopenButton]: 'Umfrage wieder öffnen',\n\t[keys.pollCloseHintManual]: 'Beim Beenden wird der ausgewählte Gewinner als Ergebnis erfasst.',\n\t[keys.pollCloseHintMostVoted]: 'Beim Beenden wird die meistgewählte Option zum Gewinner.',\n\t[keys.pollCloseHintSource]: 'Beim Beenden wird der Gewinner aus der Optionsquelle ermittelt.',\n\t[keys.pollReopenHint]:\n\t\t'Beim Wiederöffnen wird der erfasste Gewinner entfernt und es kann erneut abgestimmt werden.',\n\t[keys.pollCloseNeedsWinner]: 'Wähle zuerst einen Siegerwert.',\n\t[keys.pollCloseManualNoWinner]: 'Lege einen Gewinner fest, bevor du die Umfrage beendest.',\n\t[keys.pollWinningValue]: 'Siegerwert',\n\t[keys.pollWinningValueDescription]:\n\t\t'Wähle die Gewinneroption, sobald das Ergebnis feststeht. Beim Speichern wird der Entscheidungszeitpunkt erfasst; leeren öffnet das Ergebnis wieder.',\n\t[keys.pollResolvedAt]: 'Entschieden am',\n\t[keys.validationWinningValueUnknown]: 'Der Siegerwert muss eine der Umfrageoptionen sein.',\n\t[keys.validationWinningValueDisabled]: 'Aktiviere die Umfrage, bevor ein Ergebnis erfasst wird.',\n\t[keys.endpointOptionsLoading]: 'Optionen werden geladen...',\n\t[keys.endpointOptionsError]: 'Optionen konnten nicht geladen werden.',\n\t[keys.pollOptionsUnavailable]:\n\t\t'Umfrageoptionen sind derzeit nicht verfügbar. Bitte versuche es später erneut.',\n\t[keys.pollFinalResult]: 'Endergebnis',\n\t[keys.resultsWinner]: 'Gewinner',\n\t[keys.validationFileMissing]: 'Datei hochladen',\n\t[keys.validationFileMimeType]: 'Dateityp nicht erlaubt',\n\t[keys.validationFileTooLarge]: 'Datei ist zu groß',\n\t[keys.fieldTypeFile]: 'Datei-Upload',\n\t[keys.fileConfigMimeTypes]: 'Erlaubte Dateitypen',\n\t[keys.fileConfigMaxSize]: 'Maximale Größe (Bytes)',\n\t[keys.fileConfigMaxSizeDescription]: 'Dateien, die größer sind, werden abgelehnt.',\n\t[keys.fileTooLarge]: 'Datei ist zu groß (max. {max})',\n\t[keys.fileUploadMisconfigured]: 'Datei-Uploads sind für dieses Formular nicht konfiguriert',\n\t[keys.fileHintAccepted]: 'Akzeptiert: {types}',\n\t[keys.fileHintMaxSize]: 'Max. Größe: {max}',\n\t[keys.spamRateLimited]: 'Du hast zu viele Anfragen gesendet. Bitte versuche es später erneut.',\n\t[keys.spamRejected]: 'Deine Übermittlung konnte nicht verarbeitet werden.',\n\t[keys.spamCaptchaFailed]: 'Captcha-Überprüfung fehlgeschlagen. Bitte versuche es erneut.',\n\t[keys.collectionFormSingular]: 'Formular',\n\t[keys.collectionFormPlural]: 'Formulare',\n\t[keys.collectionSubmissionSingular]: 'Übermittlung',\n\t[keys.collectionSubmissionPlural]: 'Übermittlungen',\n\t[keys.statusComplete]: 'Vollständig',\n\t[keys.statusPartial]: 'Unvollständig',\n\t[keys.fieldTypeRepeater]: 'Wiederholungsfeld',\n\t[keys.configMinRows]: 'Minimale Zeilenanzahl',\n\t[keys.configMaxRows]: 'Maximale Zeilenanzahl',\n\t[keys.configAddLabel]: 'Beschriftung der Schaltfläche zum Hinzufügen',\n\t[keys.configSubFields]: 'Unterfelder',\n\t[keys.validationRepeaterMin]: 'Füge mindestens {min} Zeile(n) hinzu',\n\t[keys.validationRepeaterMax]: 'Entferne Zeilen, um {max} nicht zu überschreiten',\n\t[keys.repeaterAddRow]: 'Zeile hinzufügen',\n\t[keys.repeaterRemoveRow]: 'Entfernen',\n\t[keys.repeaterRow]: 'Zeile {n}',\n\t[keys.repeaterRowCount]: '{count} Zeile(n)',\n\t[keys.submissionConsent]: 'Einwilligung',\n\t[keys.submissionDetails]: 'Details zur Übermittlung',\n\t[keys.submissionConsentAgreed]: 'Zugestimmt',\n\t[keys.submissionConsentDeclined]: 'Abgelehnt',\n\t[keys.submissionMetaLocale]: 'Sprache',\n\t[keys.submissionMetaReceivedAt]: 'Empfangen am',\n\t[keys.submissionMetaIp]: 'IP-Adresse',\n\t[keys.submissionMetaUserAgent]: 'User-Agent',\n\t[keys.submissionMetaCaptcha]: 'Captcha',\n\t[keys.flowDescription]:\n\t\t'Nur für mehrstufige Formulare nötig: Felder zu Schritten gruppieren und den Ablauf dazwischen festlegen. Leer lassen, um das Formular als einzelne Seite anzuzeigen.',\n\t[keys.flowStepFallbackTitle]: 'Schritt {n}',\n\t[keys.flowFieldInStep]: 'in {step}',\n\t[keys.flowUnassigned]: 'In keinem Schritt',\n\t[keys.flowAssignToStep]: 'Zu Schritt hinzufügen',\n\t[keys.flowNextSequential]: 'Nächster Schritt in der Reihenfolge',\n\t[keys.flowNextTerminal]: 'Ende des Formulars',\n\t[keys.flowFields]: 'Felder',\n\t[keys.flowDefaultNext]: 'Standardmäßig weiter zu',\n\t[keys.flowConditionalTransitions]: 'Bedingte Übergänge',\n\t[keys.flowStepTitleLabel]: 'Titel',\n\t[keys.flowSelectStepPlaceholder]: 'Schritt auswählen…',\n\t[keys.flowMoveTransitionUp]: 'Übergang nach oben verschieben',\n\t[keys.flowMoveTransitionDown]: 'Übergang nach unten verschieben',\n\t[keys.flowRemoveTransition]: 'Übergang entfernen',\n\t[keys.flowAddAbove]: 'Oberhalb hinzufügen',\n\t[keys.flowAddBelow]: 'Unterhalb hinzufügen',\n\t[keys.flowGoTo]: 'gehe zu',\n\t[keys.flowWhen]: 'wenn',\n\t[keys.flowNoFields]: 'Noch keine Felder im Formular definiert.',\n\t[keys.flowFirstMatchWins]: '(erste Übereinstimmung gewinnt)',\n\t[keys.flowAddTransition]: 'Übergang hinzufügen',\n\t[keys.flowNoSteps]:\n\t\t'Keine Schritte definiert. Füge mindestens zwei Schritte hinzu, um die mehrseitige Ablaufsteuerung zu aktivieren.',\n\t[keys.flowFallbackTitle]: 'Ablauf',\n\t[keys.fieldTypeMessage]: 'Nachricht',\n\t[keys.configContent]: 'Inhalt',\n\t[keys.tabResponse]: 'Antwort',\n\t[keys.responseType]: 'Nach dem Absenden',\n\t[keys.responseTypeMessage]: 'Eine Nachricht anzeigen',\n\t[keys.responseTypeRedirect]: 'Zu einer URL weiterleiten',\n\t[keys.responseMessage]: 'Nachricht',\n\t[keys.responseRedirect]: 'Weiterleitung',\n\t[keys.responseUrl]: 'URL',\n\t[keys.responseRedirectReference]: 'Dokument',\n\t[keys.responseRedirectReferenceDescription]:\n\t\t'Zu einem internen Dokument statt zu einer URL weiterleiten',\n\t[keys.buttonsSubmitLabel]: 'Beschriftung der Absenden-Schaltfläche',\n\t[keys.buttonsNextLabel]: 'Beschriftung der Weiter-Schaltfläche',\n\t[keys.buttonsPrevLabel]: 'Beschriftung der Zurück-Schaltfläche',\n\t[keys.formBack]: 'Zurück',\n\t[keys.formNext]: 'Weiter',\n\t[keys.formSubmit]: 'Absenden',\n\t[keys.formMultistep]: 'Mehrstufig',\n\t[keys.formPollEnabled]: 'Umfrage',\n\t[keys.cellStepCountOne]: '{{count}} Schritt',\n\t[keys.cellStepCountOther]: '{{count}} Schritte',\n\t[keys.cellFieldCountOne]: '{{count}} Feld',\n\t[keys.cellFieldCountOther]: '{{count}} Felder',\n\t[keys.departmentsField]: 'Abteilungs-E-Mails',\n\t[keys.departmentsFieldDescription]:\n\t\t'Adressen, an die ein Formular Einsendungen weiterleiten kann, jeweils mit Bezeichnung.',\n\t[keys.departmentSingular]: 'Abteilungs-E-Mail',\n\t[keys.departmentPlural]: 'Abteilungs-E-Mails',\n\t[keys.departmentLabel]: 'Bezeichnung',\n\t[keys.departmentEmail]: 'E-Mail',\n\t[keys.departmentAddRow]: 'E-Mail hinzufügen',\n\t[keys.departmentRemoveRow]: 'E-Mail entfernen',\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"de.js","names":[],"sources":["../../src/translations/de.ts"],"sourcesContent":["import { keys, type TranslationKey } from './keys'\n\n/**\n * German values, keyed by the typed constants in `keys.ts` so the two stay in\n * lockstep. The `Record<TranslationKey, string>` annotation makes a missing or\n * unknown key a type error. `translations/index.ts` nests these for Payload.\n */\nexport const de: Record<TranslationKey, string> = {\n\t[keys.fieldTitle]: 'Titel',\n\t[keys.fieldTypeText]: 'Text',\n\t[keys.fieldTypeTextarea]: 'Textarea',\n\t[keys.fieldTypeEmail]: 'E-Mail',\n\t[keys.fieldTypeNumber]: 'Zahl',\n\t[keys.fieldTypeSelect]: 'Auswahl',\n\t[keys.fieldTypeCountry]: 'Land',\n\t[keys.fieldTypeState]: 'Bundesstaat',\n\t[keys.fieldTypeCheckbox]: 'Checkbox',\n\t[keys.fieldTypeDate]: 'Datum',\n\t[keys.configOptions]: 'Optionen',\n\t[keys.configOption]: 'Option',\n\t[keys.configOptionLabel]: 'Bezeichnung',\n\t[keys.configOptionValue]: 'Wert',\n\t[keys.validationRequired]: 'Dieses Feld ist erforderlich',\n\t[keys.validationEmail]: 'Gib eine gültige E-Mail-Adresse ein',\n\t[keys.validationNumber]: 'Gib eine gültige Zahl ein',\n\t[keys.validationDate]: 'Gib ein gültiges Datum ein',\n\t[keys.validationSelect]: 'Wähle eine gültige Option',\n\t[keys.validationCountry]: 'Wähle ein gültiges Land',\n\t[keys.validationState]: 'Wähle einen gültigen Bundesstaat',\n\t[keys.validationRegexPattern]: 'Gib einen gültigen regulären Ausdruck ein',\n\t[keys.validationRegexFlags]:\n\t\t'Gib gültige Flags für reguläre Ausdrücke ein, zum Beispiel i oder gi',\n\t[keys.validationEmailFieldUnknown]: 'Wähle ein bestehendes E-Mail-Feld dieses Formulars',\n\t[keys.validationResultsFieldUnknown]: 'Wähle ein geeignetes Auswahlfeld dieses Formulars',\n\t[keys.formatYes]: 'Ja',\n\t[keys.formatNo]: 'Nein',\n\t[keys.configName]: 'Name',\n\t[keys.configLabel]: 'Bezeichnung',\n\t[keys.configRequired]: 'Erforderlich',\n\t[keys.configWidth]: 'Breite',\n\t[keys.configPlaceholder]: 'Platzhalter',\n\t[keys.configDescription]: 'Beschreibung',\n\t[keys.configVisibleWhen]: 'Dieses Feld anzeigen, wenn',\n\t[keys.configValidateWhen]: 'Dieses Feld nur validieren, wenn',\n\t[keys.submissionAnswers]: 'Antworten',\n\t[keys.submissionNoAnswers]: 'Keine Antworten',\n\t[keys.ruleMinLength]: 'Minimale Länge',\n\t[keys.ruleMaxLength]: 'Maximale Länge',\n\t[keys.ruleMin]: 'Minimum',\n\t[keys.ruleMax]: 'Maximum',\n\t[keys.ruleMinDate]: 'Frühestes Datum',\n\t[keys.ruleMaxDate]: 'Spätestes Datum',\n\t[keys.rulePattern]: 'Muster',\n\t[keys.ruleEmail]: 'E-Mail',\n\t[keys.ruleUrl]: 'URL',\n\t[keys.ruleOneOf]: 'Wert aus Liste',\n\t[keys.ruleMatchesField]: 'Stimmt mit Feld überein',\n\t[keys.ruleNotAlreadySubmitted]: 'Noch nicht übermittelt',\n\t[keys.ruleMinLengthMessage]: 'Muss mindestens {min} Zeichen lang sein',\n\t[keys.ruleMaxLengthMessage]: 'Darf höchstens {max} Zeichen lang sein',\n\t[keys.ruleMinMessage]: 'Muss mindestens {min} betragen',\n\t[keys.ruleMaxMessage]: 'Darf höchstens {max} betragen',\n\t[keys.ruleMinDateMessage]: 'Muss am oder nach dem {min} liegen',\n\t[keys.ruleMaxDateMessage]: 'Muss am oder vor dem {max} liegen',\n\t[keys.rulePatternMessage]: 'Ungültiges Format',\n\t[keys.ruleEmailMessage]: 'Gib eine gültige E-Mail-Adresse ein',\n\t[keys.ruleUrlMessage]: 'Gib eine gültige URL ein',\n\t[keys.ruleOneOfMessage]: 'Wähle einen zulässigen Wert',\n\t[keys.ruleMatchesFieldMessage]: 'Stimmt nicht überein',\n\t[keys.ruleNotAlreadySubmittedMessage]: 'Dieser Wert wurde bereits übermittelt',\n\t[keys.ruleMinLengthDescription]:\n\t\t'Schlägt fehl, wenn der eingegebene Text kürzer als die Mindestanzahl an Zeichen ist.',\n\t[keys.ruleMaxLengthDescription]:\n\t\t'Schlägt fehl, wenn der eingegebene Text länger als die maximale Anzahl an Zeichen ist.',\n\t[keys.ruleMinDescription]: 'Schlägt fehl, wenn die eingegebene Zahl unter dem Minimum liegt.',\n\t[keys.ruleMaxDescription]: 'Schlägt fehl, wenn die eingegebene Zahl über dem Maximum liegt.',\n\t[keys.ruleMinDateDescription]:\n\t\t'Schlägt fehl, wenn das gewählte Datum vor dem frühesten Datum liegt.',\n\t[keys.ruleMaxDateDescription]:\n\t\t'Schlägt fehl, wenn das gewählte Datum nach dem spätesten Datum liegt.',\n\t[keys.rulePatternDescription]:\n\t\t'Schlägt fehl, wenn der eingegebene Text nicht zum regulären Ausdruck passt.',\n\t[keys.ruleEmailDescription]:\n\t\t'Schlägt fehl, wenn der eingegebene Wert keine gültige E-Mail-Adresse ist.',\n\t[keys.ruleUrlDescription]:\n\t\t'Schlägt fehl, wenn der eingegebene Wert keine gültige http- oder https-URL ist.',\n\t[keys.ruleOneOfDescription]:\n\t\t'Schlägt fehl, wenn der eingegebene Wert keiner der von dir angegebenen zulässigen Werte ist.',\n\t[keys.ruleMatchesFieldDescription]:\n\t\t'Schlägt fehl, wenn der Wert dieses Feldes nicht dem gewählten Feld entspricht. Nutze es für E-Mail- oder Passwort-Bestätigung.',\n\t[keys.ruleNotAlreadySubmittedDescription]:\n\t\t'Schlägt fehl, wenn genau dieser Wert bereits an dieses Formular übermittelt wurde (serverseitig geprüft).',\n\t[keys.ruleFieldTargetInvalid]:\n\t\t'Das ausgewählte Feld existiert nicht mehr. Wähle ein gültiges Feld.',\n\t[keys.ruleParamMin]: 'Minimum',\n\t[keys.ruleParamMax]: 'Maximum',\n\t[keys.ruleParamMinDate]: 'Frühestes Datum (JJJJ-MM-TT)',\n\t[keys.ruleParamMaxDate]: 'Spätestes Datum (JJJJ-MM-TT)',\n\t[keys.ruleParamPattern]: 'Muster',\n\t[keys.ruleParamFlags]: 'Flags',\n\t[keys.ruleParamValues]: 'Zulässige Werte',\n\t[keys.ruleParamField]: 'Feldname',\n\t[keys.validationsLabel]: 'Validierungsregeln',\n\t[keys.validationMessageLabel]: 'Benutzerdefinierte Nachricht',\n\t[keys.conditionAddCondition]: 'Bedingung hinzufügen',\n\t[keys.conditionAddOr]: '\"Oder\"-Gruppe hinzufügen',\n\t[keys.conditionAnd]: 'Und',\n\t[keys.conditionOr]: 'Oder',\n\t[keys.conditionRemove]: 'Entfernen',\n\t[keys.conditionNoFields]:\n\t\t'Füge diesem Formular benannte Felder hinzu, um eine Bedingung zu erstellen.',\n\t[keys.conditionEmpty]: 'Keine Bedingungen. Dieses Feld wird immer angezeigt.',\n\t[keys.conditionSelectField]: 'Feld auswählen',\n\t[keys.conditionTrue]: 'Wahr',\n\t[keys.conditionFalse]: 'Falsch',\n\t[keys.configHidden]: 'Ausgeblendet (wird erfasst, aber nicht angezeigt)',\n\t[keys.tabFields]: 'Felder',\n\t[keys.tabFlow]: 'Ablauf',\n\t[keys.tabActions]: 'Aktionen',\n\t[keys.tabField]: 'Feld',\n\t[keys.tabValidation]: 'Validierung',\n\t[keys.tabAdvanced]: 'Erweitert',\n\t[keys.fieldTypeCalculation]: 'Berechnung',\n\t[keys.configExpression]: 'Ausdruck (JSON)',\n\t[keys.configExpressionDescription]:\n\t\t'Ausdrucksbaum aus Knoten {\"type\":\"lit\"|\"ref\"|\"op\"|\"neg\"|\"fn\"|\"weight\"}. Beispiel: {\"type\":\"op\",\"op\":\"+\",\"left\":{\"type\":\"ref\",\"field\":\"qty\"},\"right\":{\"type\":\"lit\",\"value\":10}}',\n\t[keys.configCalcDisplay]: 'Berechneten Wert anzeigen',\n\t[keys.validationCalcExpressionInvalid]: 'Gib einen gültigen Berechnungsausdruck ein',\n\t[keys.presentationPage]: 'Seite',\n\t[keys.presentationModal]: 'Modal',\n\t[keys.presentationDrawer]: 'Seitenpanel',\n\t[keys.presentationInline]: 'Inline',\n\t[keys.actionEmailTeam]: 'E-Mail ans Team',\n\t[keys.actionConfirmation]: 'Bestätigungs-E-Mail',\n\t[keys.actionSignedWebhook]: 'Signierter Webhook',\n\t[keys.actionConfigTo]: 'An',\n\t[keys.actionConfigSubject]: 'Betreff',\n\t[keys.actionConfigBody]: 'Nachrichtentext',\n\t[keys.actionConfigBodyDescription]:\n\t\t'Unterstützt {{ fieldName|fallback }}-Platzhalter, {{*}} für alle Antworten als Zeilen und {{*:table}} für alle Antworten als Tabelle.',\n\t[keys.actionConfigToField]: 'Name des E-Mail-Feldes',\n\t[keys.actionConfigToFieldDescription]:\n\t\t'Das E-Mail-Feld dieses Formulars, an das die Bestätigung gesendet wird.',\n\t[keys.actionConfigFrom]: 'Von',\n\t[keys.actionConfigFromDescription]:\n\t\t'Absenderadresse für diese Aktion. Leer lassen, um den Standard des E-Mail-Adapters zu verwenden.',\n\t[keys.actionConfigCc]: 'CC',\n\t[keys.actionConfigBcc]: 'BCC',\n\t[keys.actionConfigReplyTo]: 'Antwort an',\n\t[keys.recipientsGroupDepartments]: 'Abteilungen',\n\t[keys.recipientsGroupFields]: 'Formularfelder',\n\t[keys.validationRecipientInvalid]: 'Gib eine gültige E-Mail-Adresse ein.',\n\t[keys.validationRecipientUnknownField]: 'Verweist auf ein nicht mehr vorhandenes Feld.',\n\t[keys.validationRecipientNotAllowed]: 'Dieser Empfänger steht nicht auf der zulässigen Liste.',\n\t[keys.validationRecipientOptionsUnavailable]:\n\t\t'Empfängeroptionen sind derzeit nicht verfügbar. Bitte versuche es später erneut.',\n\t[keys.validationFromUnknown]: 'Wähle eine der konfigurierten Absenderadressen',\n\t[keys.validationFromUnavailable]:\n\t\t'Absenderadressen sind derzeit nicht verfügbar. Bitte versuche es später erneut.',\n\t[keys.actionConfigUrl]: 'URL',\n\t[keys.actionConfigUrlDescription]:\n\t\t'Der Endpunkt, der für jede Übermittlung einen signierten JSON-POST erhält.',\n\t[keys.actionConfigSecret]: 'Geheimer Schlüssel',\n\t[keys.actionConfigSecretDescription]:\n\t\t'HMAC-Schlüssel für den X-Form-Signature-Header, der mit dem Empfänger geteilt wird.',\n\t[keys.validationUrlInvalid]: 'Gib eine gültige http- oder https-URL ein',\n\t[keys.configActions]: 'Aktionen',\n\t[keys.fieldTypeConsent]: 'Einwilligung',\n\t[keys.consentConfigSource]: 'Quelle',\n\t[keys.consentConfigSourceDescription]:\n\t\t'Die Erklärung, der Besucher zustimmen. Wortlaut und Rechtsseite gehören zur Quelle: eine Änderung dort gilt für jedes Formular, das sie nutzt.',\n\t[keys.consentSourcesField]: 'Einwilligungsquellen',\n\t[keys.consentSourcesFieldDescription]:\n\t\t'Erklärungen, die Formulare in Einwilligungsfeldern verwenden können',\n\t[keys.consentSourceSingular]: 'Einwilligungsquelle',\n\t[keys.consentSourcePlural]: 'Einwilligungsquellen',\n\t[keys.consentSourceLabel]: 'Name',\n\t[keys.consentSourceStatement]: 'Erklärung',\n\t[keys.consentSourcePage]: 'Erklärungsquelle',\n\t[keys.consentSourcePageDescription]:\n\t\t'Muss gesetzt sein, wenn Formulareinsendungen einen Verweis auf deine Richtlinie speichern sollen.',\n\t[keys.consentSourcesUnavailable]:\n\t\t'Einwilligungsquellen sind nicht verfügbar. Versuche es gleich noch einmal.',\n\t[keys.resultsResponses]: 'Antworten',\n\t[keys.resultsNoResponses]: 'Noch keine Antworten',\n\t[keys.resultsTruncated]: 'Zeigt eine Stichprobe der Antworten',\n\t[keys.pollGroup]: 'Umfrage',\n\t[keys.pollResultsField]: 'Abstimmungsfeld',\n\t[keys.pollResultsFieldDescription]:\n\t\t'Das Auswahlfeld, dessen Antworten als Stimmen gezählt werden. Wird automatisch gewählt, wenn dein Formular genau ein Auswahlfeld hat. Verwende ein Auswahlfeld, niemals ein Freitext- oder personenbezogenes Feld.',\n\t[keys.pollVoteFieldChoose]: 'Wähle das Feld, dessen Antworten als Stimmen zählen.',\n\t[keys.pollVoteFieldMissing]: 'Füge ein Auswahlfeld als Abstimmungsfrage hinzu.',\n\t[keys.pollResultsVisibility]: 'Sichtbarkeit der Ergebnisse',\n\t[keys.pollVisibilityAfterVote]: 'Nach der Abstimmung',\n\t[keys.pollVisibilityAfterClose]: 'Nach Ende der Umfrage',\n\t[keys.pollClosesAt]: 'Endet am',\n\t[keys.pollClosed]: 'Diese Umfrage ist beendet.',\n\t[keys.pollResultsAfterClose]: 'Die Ergebnisse werden nach Ende der Umfrage angezeigt.',\n\t[keys.pollOptionSource]: 'Optionsquelle',\n\t[keys.pollOptionSourceDescription]:\n\t\t'Befüllt die Auswahlmöglichkeiten des Ergebnisfeldes mit App-Daten anstelle manuell erstellter Optionen.',\n\t[keys.pollSourceConfig]: 'Quelleinstellungen',\n\t[keys.pollOutcome]: 'Ergebnis',\n\t[keys.pollType]: 'Ergebnistyp',\n\t[keys.pollTypeDescription]: 'Wie die Gewinneroption bestimmt wird, sobald die Umfrage endet.',\n\t[keys.pollTypeManual]: 'Gewinner manuell festlegen',\n\t[keys.pollTypeMostVoted]: 'Meistgewählte Option gewinnt',\n\t[keys.pollTypeSource]: 'Gewinner aus der Optionsquelle',\n\t[keys.pollCloseButton]: 'Umfrage jetzt beenden',\n\t[keys.pollReopenButton]: 'Umfrage wieder öffnen',\n\t[keys.pollCloseHintManual]: 'Beim Beenden wird der ausgewählte Gewinner als Ergebnis erfasst.',\n\t[keys.pollCloseHintMostVoted]: 'Beim Beenden wird die meistgewählte Option zum Gewinner.',\n\t[keys.pollCloseHintSource]: 'Beim Beenden wird der Gewinner aus der Optionsquelle ermittelt.',\n\t[keys.pollReopenHint]:\n\t\t'Beim Wiederöffnen wird der erfasste Gewinner entfernt und es kann erneut abgestimmt werden.',\n\t[keys.pollCloseNeedsWinner]: 'Wähle zuerst einen Siegerwert.',\n\t[keys.pollCloseManualNoWinner]: 'Lege einen Gewinner fest, bevor du die Umfrage beendest.',\n\t[keys.pollWinningValue]: 'Siegerwert',\n\t[keys.pollWinningValueDescription]:\n\t\t'Wähle die Gewinneroption, sobald das Ergebnis feststeht. Beim Speichern wird der Entscheidungszeitpunkt erfasst; leeren öffnet das Ergebnis wieder.',\n\t[keys.pollResolvedAt]: 'Entschieden am',\n\t[keys.validationWinningValueUnknown]: 'Der Siegerwert muss eine der Umfrageoptionen sein.',\n\t[keys.validationWinningValueDisabled]: 'Aktiviere die Umfrage, bevor ein Ergebnis erfasst wird.',\n\t[keys.endpointOptionsLoading]: 'Optionen werden geladen...',\n\t[keys.endpointOptionsError]: 'Optionen konnten nicht geladen werden.',\n\t[keys.pollOptionsUnavailable]:\n\t\t'Umfrageoptionen sind derzeit nicht verfügbar. Bitte versuche es später erneut.',\n\t[keys.pollFinalResult]: 'Endergebnis',\n\t[keys.pollResultsError]: 'Ergebnisse konnten nicht geladen werden.',\n\t[keys.resultsWinner]: 'Gewinner',\n\t[keys.validationFileMissing]: 'Datei hochladen',\n\t[keys.validationFileMimeType]: 'Dateityp nicht erlaubt',\n\t[keys.validationFileTooLarge]: 'Datei ist zu groß',\n\t[keys.fieldTypeFile]: 'Datei-Upload',\n\t[keys.fileConfigMimeTypes]: 'Erlaubte Dateitypen',\n\t[keys.fileConfigMaxSize]: 'Maximale Größe (Bytes)',\n\t[keys.fileConfigMaxSizeDescription]: 'Dateien, die größer sind, werden abgelehnt.',\n\t[keys.fileTooLarge]: 'Datei ist zu groß (max. {max})',\n\t[keys.fileUploadMisconfigured]: 'Datei-Uploads sind für dieses Formular nicht konfiguriert',\n\t[keys.fileHintAccepted]: 'Akzeptiert: {types}',\n\t[keys.fileHintMaxSize]: 'Max. Größe: {max}',\n\t[keys.fileUploaded]: 'Hochgeladene Datei',\n\t[keys.fileUploading]: 'Wird hochgeladen',\n\t[keys.fileUploadFailed]: 'Upload fehlgeschlagen',\n\t[keys.fileRemove]: 'Entfernen',\n\t[keys.spamRateLimited]: 'Du hast zu viele Anfragen gesendet. Bitte versuche es später erneut.',\n\t[keys.spamRejected]: 'Deine Übermittlung konnte nicht verarbeitet werden.',\n\t[keys.spamCaptchaFailed]: 'Captcha-Überprüfung fehlgeschlagen. Bitte versuche es erneut.',\n\t[keys.collectionFormSingular]: 'Formular',\n\t[keys.collectionFormPlural]: 'Formulare',\n\t[keys.collectionSubmissionSingular]: 'Übermittlung',\n\t[keys.collectionSubmissionPlural]: 'Übermittlungen',\n\t[keys.statusComplete]: 'Vollständig',\n\t[keys.statusPartial]: 'Unvollständig',\n\t[keys.fieldTypeRepeater]: 'Wiederholungsfeld',\n\t[keys.configMinRows]: 'Minimale Zeilenanzahl',\n\t[keys.configMaxRows]: 'Maximale Zeilenanzahl',\n\t[keys.configAddLabel]: 'Beschriftung der Schaltfläche zum Hinzufügen',\n\t[keys.configSubFields]: 'Unterfelder',\n\t[keys.validationRepeaterMin]: 'Füge mindestens {min} Zeile(n) hinzu',\n\t[keys.validationRepeaterMax]: 'Entferne Zeilen, um {max} nicht zu überschreiten',\n\t[keys.repeaterAddRow]: 'Zeile hinzufügen',\n\t[keys.repeaterRemoveRow]: 'Entfernen',\n\t[keys.repeaterRow]: 'Zeile {n}',\n\t[keys.repeaterRowCount]: '{count} Zeile(n)',\n\t[keys.submissionConsent]: 'Einwilligung',\n\t[keys.submissionDetails]: 'Details zur Übermittlung',\n\t[keys.submissionConsentAgreed]: 'Zugestimmt',\n\t[keys.submissionConsentDeclined]: 'Abgelehnt',\n\t[keys.submissionMetaLocale]: 'Sprache',\n\t[keys.submissionMetaReceivedAt]: 'Empfangen am',\n\t[keys.submissionMetaIp]: 'IP-Adresse',\n\t[keys.submissionMetaUserAgent]: 'User-Agent',\n\t[keys.submissionMetaCaptcha]: 'Captcha',\n\t[keys.flowDescription]:\n\t\t'Nur für mehrstufige Formulare nötig: Felder zu Schritten gruppieren und den Ablauf dazwischen festlegen. Leer lassen, um das Formular als einzelne Seite anzuzeigen.',\n\t[keys.flowStepFallbackTitle]: 'Schritt {n}',\n\t[keys.flowFieldInStep]: 'in {step}',\n\t[keys.flowUnassigned]: 'In keinem Schritt',\n\t[keys.flowAssignToStep]: 'Zu Schritt hinzufügen',\n\t[keys.flowNextSequential]: 'Nächster Schritt in der Reihenfolge',\n\t[keys.flowNextTerminal]: 'Ende des Formulars',\n\t[keys.flowFields]: 'Felder',\n\t[keys.flowDefaultNext]: 'Standardmäßig weiter zu',\n\t[keys.flowConditionalTransitions]: 'Bedingte Übergänge',\n\t[keys.flowStepTitleLabel]: 'Titel',\n\t[keys.flowSelectStepPlaceholder]: 'Schritt auswählen…',\n\t[keys.flowMoveTransitionUp]: 'Übergang nach oben verschieben',\n\t[keys.flowMoveTransitionDown]: 'Übergang nach unten verschieben',\n\t[keys.flowRemoveTransition]: 'Übergang entfernen',\n\t[keys.flowAddAbove]: 'Oberhalb hinzufügen',\n\t[keys.flowAddBelow]: 'Unterhalb hinzufügen',\n\t[keys.flowGoTo]: 'gehe zu',\n\t[keys.flowWhen]: 'wenn',\n\t[keys.flowNoFields]: 'Noch keine Felder im Formular definiert.',\n\t[keys.flowFirstMatchWins]: '(erste Übereinstimmung gewinnt)',\n\t[keys.flowAddTransition]: 'Übergang hinzufügen',\n\t[keys.flowNoSteps]:\n\t\t'Keine Schritte definiert. Füge mindestens zwei Schritte hinzu, um die mehrseitige Ablaufsteuerung zu aktivieren.',\n\t[keys.flowFallbackTitle]: 'Ablauf',\n\t[keys.fieldTypeMessage]: 'Nachricht',\n\t[keys.configContent]: 'Inhalt',\n\t[keys.tabResponse]: 'Antwort',\n\t[keys.responseType]: 'Nach dem Absenden',\n\t[keys.responseTypeMessage]: 'Eine Nachricht anzeigen',\n\t[keys.responseTypeRedirect]: 'Zu einer URL weiterleiten',\n\t[keys.responseMessage]: 'Nachricht',\n\t[keys.responseRedirect]: 'Weiterleitung',\n\t[keys.responseUrl]: 'URL',\n\t[keys.responseRedirectReference]: 'Dokument',\n\t[keys.responseRedirectReferenceDescription]:\n\t\t'Zu einem internen Dokument statt zu einer URL weiterleiten',\n\t[keys.buttonsSubmitLabel]: 'Beschriftung der Absenden-Schaltfläche',\n\t[keys.buttonsNextLabel]: 'Beschriftung der Weiter-Schaltfläche',\n\t[keys.buttonsPrevLabel]: 'Beschriftung der Zurück-Schaltfläche',\n\t[keys.formBack]: 'Zurück',\n\t[keys.formNext]: 'Weiter',\n\t[keys.formSubmit]: 'Absenden',\n\t[keys.formMultistep]: 'Mehrstufig',\n\t[keys.formPollEnabled]: 'Umfrage',\n\t[keys.formClose]: 'Schließen',\n\t[keys.formSuccess]: 'Vielen Dank.',\n\t[keys.formSubmitFailed]: 'Übermittlung fehlgeschlagen',\n\t[keys.cellStepCountOne]: '{{count}} Schritt',\n\t[keys.cellStepCountOther]: '{{count}} Schritte',\n\t[keys.cellFieldCountOne]: '{{count}} Feld',\n\t[keys.cellFieldCountOther]: '{{count}} Felder',\n\t[keys.departmentsField]: 'Abteilungs-E-Mails',\n\t[keys.departmentsFieldDescription]:\n\t\t'Adressen, an die ein Formular Einsendungen weiterleiten kann, jeweils mit Bezeichnung.',\n\t[keys.departmentSingular]: 'Abteilungs-E-Mail',\n\t[keys.departmentPlural]: 'Abteilungs-E-Mails',\n\t[keys.departmentLabel]: 'Bezeichnung',\n\t[keys.departmentEmail]: 'E-Mail',\n\t[keys.departmentAddRow]: 'E-Mail hinzufügen',\n\t[keys.departmentRemoveRow]: 'E-Mail entfernen',\n}\n"],"mappings":"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"}