@10x-media/form-builder 0.1.0-beta.5 → 0.1.0-beta.7

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 (213) hide show
  1. package/CHANGELOG.md +102 -0
  2. package/dist/actions/buildActionBlocks.js +12 -9
  3. package/dist/actions/buildActionBlocks.js.map +1 -1
  4. package/dist/actions/builtin/confirmation.js +27 -79
  5. package/dist/actions/builtin/confirmation.js.map +1 -1
  6. package/dist/actions/builtin/emailAction.d.ts +17 -0
  7. package/dist/actions/builtin/emailAction.js +83 -0
  8. package/dist/actions/builtin/emailAction.js.map +1 -0
  9. package/dist/actions/builtin/emailTeam.js +14 -84
  10. package/dist/actions/builtin/emailTeam.js.map +1 -1
  11. package/dist/actions/builtin/index.d.ts +9 -11
  12. package/dist/actions/builtin/index.js +11 -11
  13. package/dist/actions/builtin/index.js.map +1 -1
  14. package/dist/actions/defineAction.d.ts +2 -2
  15. package/dist/actions/defineAction.js.map +1 -1
  16. package/dist/actions/emailRecipients.d.ts +12 -0
  17. package/dist/actions/emailRecipients.js +116 -0
  18. package/dist/actions/emailRecipients.js.map +1 -0
  19. package/dist/actions/registry.d.ts +2 -1
  20. package/dist/actions/registry.js +2 -6
  21. package/dist/actions/registry.js.map +1 -1
  22. package/dist/actions/task.js +5 -3
  23. package/dist/actions/task.js.map +1 -1
  24. package/dist/calc/evaluate.js +1 -2
  25. package/dist/calc/evaluate.js.map +1 -1
  26. package/dist/calc/normalizeCalc.js +1 -2
  27. package/dist/calc/normalizeCalc.js.map +1 -1
  28. package/dist/calc/types.js.map +1 -1
  29. package/dist/client/ConsentBlockLabel.d.ts +15 -0
  30. package/dist/client/ConsentBlockLabel.js +71 -0
  31. package/dist/client/ConsentBlockLabel.js.map +1 -0
  32. package/dist/client/ConsentSourceRowLabel.d.ts +9 -0
  33. package/dist/client/ConsentSourceRowLabel.js +21 -0
  34. package/dist/client/ConsentSourceRowLabel.js.map +1 -0
  35. package/dist/client/EndpointOptionsSelect.d.ts +1 -0
  36. package/dist/client/EndpointOptionsSelect.js +4 -1
  37. package/dist/client/EndpointOptionsSelect.js.map +1 -1
  38. package/dist/client/FieldNameSelect.d.ts +1 -0
  39. package/dist/client/FieldNameSelect.js +4 -1
  40. package/dist/client/FieldNameSelect.js.map +1 -1
  41. package/dist/client/RecipientsSelect.d.ts +31 -0
  42. package/dist/client/RecipientsSelect.js +174 -0
  43. package/dist/client/RecipientsSelect.js.map +1 -0
  44. package/dist/client/admin.css +5 -0
  45. package/dist/collections/buttonFields.js +1 -1
  46. package/dist/collections/formSubmissions.js +4 -2
  47. package/dist/collections/formSubmissions.js.map +1 -1
  48. package/dist/collections/forms.js +46 -99
  49. package/dist/collections/forms.js.map +1 -1
  50. package/dist/collections/formsEndpoints.js +101 -0
  51. package/dist/collections/formsEndpoints.js.map +1 -0
  52. package/dist/conditions/evaluate.js +1 -1
  53. package/dist/conditions/evaluate.js.map +1 -1
  54. package/dist/conditions/fieldTypes.d.ts +3 -1
  55. package/dist/conditions/fieldTypes.js +4 -1
  56. package/dist/conditions/fieldTypes.js.map +1 -1
  57. package/dist/conditions/normalizeConditions.js +18 -5
  58. package/dist/conditions/normalizeConditions.js.map +1 -1
  59. package/dist/consent/captureConsent.d.ts +21 -5
  60. package/dist/consent/captureConsent.js +31 -7
  61. package/dist/consent/captureConsent.js.map +1 -1
  62. package/dist/consent/consentSourcesField.d.ts +8 -0
  63. package/dist/consent/consentSourcesField.js +13 -2
  64. package/dist/consent/consentSourcesField.js.map +1 -1
  65. package/dist/consent/resolveConsentEntries.d.ts +1 -1
  66. package/dist/consent/resolveConsentEntries.js +1 -4
  67. package/dist/consent/resolveConsentEntries.js.map +1 -1
  68. package/dist/consent/resolveConsentSourcesRequest.js +0 -1
  69. package/dist/consent/resolveConsentSourcesRequest.js.map +1 -1
  70. package/dist/consent/resolveConsentStatements.d.ts +1 -1
  71. package/dist/consent/resolveConsentStatements.js.map +1 -1
  72. package/dist/consent/types.d.ts +13 -1
  73. package/dist/email/departments.d.ts +13 -9
  74. package/dist/email/departments.js +7 -50
  75. package/dist/email/departments.js.map +1 -1
  76. package/dist/email/departmentsField.d.ts +4 -0
  77. package/dist/email/departmentsField.js +5 -1
  78. package/dist/email/departmentsField.js.map +1 -1
  79. package/dist/exports/client.d.ts +4 -1
  80. package/dist/exports/client.js +4 -1
  81. package/dist/exports/react.d.ts +3 -2
  82. package/dist/exports/react.js +4 -3
  83. package/dist/exports/rsc.js +1 -1
  84. package/dist/exports/types.d.ts +1 -1
  85. package/dist/fields/buildFieldBlocks.js +4 -4
  86. package/dist/fields/buildFieldBlocks.js.map +1 -1
  87. package/dist/fields/builtin/consent.js +6 -1
  88. package/dist/fields/builtin/consent.js.map +1 -1
  89. package/dist/fields/builtin/email.js +1 -1
  90. package/dist/fields/builtin/email.js.map +1 -1
  91. package/dist/fields/builtin/file.js +9 -3
  92. package/dist/fields/builtin/file.js.map +1 -1
  93. package/dist/fields/builtin/index.d.ts +1 -1
  94. package/dist/fields/builtin/index.js +3 -3
  95. package/dist/fields/builtin/index.js.map +1 -1
  96. package/dist/fields/builtin/message.js +1 -1
  97. package/dist/fields/builtin/repeater.js +1 -1
  98. package/dist/fields/builtin/select.js +1 -1
  99. package/dist/fields/registry.d.ts +2 -1
  100. package/dist/fields/registry.js +2 -6
  101. package/dist/fields/registry.js.map +1 -1
  102. package/dist/fields/sharedConfig.js +1 -1
  103. package/dist/fields/types.d.ts +9 -2
  104. package/dist/flow/normalizeFlow.js +7 -3
  105. package/dist/flow/normalizeFlow.js.map +1 -1
  106. package/dist/form/toFormDocument.d.ts +20 -3
  107. package/dist/form/toFormDocument.js +16 -4
  108. package/dist/form/toFormDocument.js.map +1 -1
  109. package/dist/form/types.d.ts +5 -5
  110. package/dist/index.d.ts +5 -193
  111. package/dist/index.js +18 -8
  112. package/dist/index.js.map +1 -1
  113. package/dist/options.d.ts +228 -0
  114. package/dist/plugin/applyRegistryConfig.d.ts +11 -0
  115. package/dist/plugin/applyRegistryConfig.js +20 -0
  116. package/dist/plugin/applyRegistryConfig.js.map +1 -0
  117. package/dist/plugin/registerCollections.js +3 -1
  118. package/dist/plugin/registerCollections.js.map +1 -1
  119. package/dist/plugin/uploadsCollection.d.ts +3 -0
  120. package/dist/plugin/uploadsCollection.js +10 -1
  121. package/dist/plugin/uploadsCollection.js.map +1 -1
  122. package/dist/poll/buildPollOptionSourceFields.js +2 -2
  123. package/dist/poll/buildPollOptionSourceFields.js.map +1 -1
  124. package/dist/poll/definePollOptionSource.d.ts +2 -2
  125. package/dist/poll/definePollOptionSource.js.map +1 -1
  126. package/dist/poll/pollTypeRegistry.js +2 -4
  127. package/dist/poll/pollTypeRegistry.js.map +1 -1
  128. package/dist/poll/registry.d.ts +2 -1
  129. package/dist/poll/registry.js +2 -6
  130. package/dist/poll/registry.js.map +1 -1
  131. package/dist/poll/resolvePollOptionsRequest.js +0 -1
  132. package/dist/poll/resolvePollOptionsRequest.js.map +1 -1
  133. package/dist/prefill/valuesFromSearchParams.js +2 -7
  134. package/dist/prefill/valuesFromSearchParams.js.map +1 -1
  135. package/dist/react/Form.d.ts +3 -1
  136. package/dist/react/Form.js +93 -155
  137. package/dist/react/Form.js.map +1 -1
  138. package/dist/react/FormContext.d.ts +5 -1
  139. package/dist/react/FormContext.js.map +1 -1
  140. package/dist/react/FormFields.d.ts +18 -0
  141. package/dist/react/FormFields.js +114 -0
  142. package/dist/react/FormFields.js.map +1 -0
  143. package/dist/react/FormLayout.d.ts +4 -2
  144. package/dist/react/FormLayout.js +3 -2
  145. package/dist/react/FormLayout.js.map +1 -1
  146. package/dist/react/Poll.js +13 -3
  147. package/dist/react/Poll.js.map +1 -1
  148. package/dist/react/presentation/DialogSurface.js +1 -4
  149. package/dist/react/presentation/DialogSurface.js.map +1 -1
  150. package/dist/react/renderers/calculation.js +1 -3
  151. package/dist/react/renderers/calculation.js.map +1 -1
  152. package/dist/react/renderers/checkbox.js +1 -3
  153. package/dist/react/renderers/checkbox.js.map +1 -1
  154. package/dist/react/renderers/consent.js +2 -3
  155. package/dist/react/renderers/consent.js.map +1 -1
  156. package/dist/react/renderers/date.js +1 -3
  157. package/dist/react/renderers/date.js.map +1 -1
  158. package/dist/react/renderers/email.js +1 -3
  159. package/dist/react/renderers/email.js.map +1 -1
  160. package/dist/react/renderers/file.js +6 -7
  161. package/dist/react/renderers/file.js.map +1 -1
  162. package/dist/react/renderers/number.js +1 -3
  163. package/dist/react/renderers/number.js.map +1 -1
  164. package/dist/react/renderers/region.js +1 -3
  165. package/dist/react/renderers/region.js.map +1 -1
  166. package/dist/react/renderers/repeater.js +20 -4
  167. package/dist/react/renderers/repeater.js.map +1 -1
  168. package/dist/react/renderers/select.js +1 -3
  169. package/dist/react/renderers/select.js.map +1 -1
  170. package/dist/react/renderers/text.js +1 -3
  171. package/dist/react/renderers/text.js.map +1 -1
  172. package/dist/react/renderers/textarea.js +1 -3
  173. package/dist/react/renderers/textarea.js.map +1 -1
  174. package/dist/spam/constants.js +7 -1
  175. package/dist/spam/constants.js.map +1 -1
  176. package/dist/spam/resolveSpam.js +2 -1
  177. package/dist/spam/resolveSpam.js.map +1 -1
  178. package/dist/spam/spamGuard.js +22 -6
  179. package/dist/spam/spamGuard.js.map +1 -1
  180. package/dist/spam/types.d.ts +17 -2
  181. package/dist/submissions/SubmissionAnswers.js.map +1 -1
  182. package/dist/submissions/SubmissionAnswersClient.js +33 -26
  183. package/dist/submissions/SubmissionAnswersClient.js.map +1 -1
  184. package/dist/submissions/coerceBoolean.js +14 -0
  185. package/dist/submissions/coerceBoolean.js.map +1 -0
  186. package/dist/submissions/runSubmission.js +9 -8
  187. package/dist/submissions/runSubmission.js.map +1 -1
  188. package/dist/submissions/validateSubmission.js +4 -2
  189. package/dist/submissions/validateSubmission.js.map +1 -1
  190. package/dist/translations/de.js +14 -2
  191. package/dist/translations/de.js.map +1 -1
  192. package/dist/translations/en.js +14 -2
  193. package/dist/translations/en.js.map +1 -1
  194. package/dist/translations/keys.d.ts +14 -2
  195. package/dist/translations/keys.js +14 -2
  196. package/dist/translations/keys.js.map +1 -1
  197. package/dist/translations/server.js +8 -1
  198. package/dist/translations/server.js.map +1 -1
  199. package/dist/uploads/captureFileRef.d.ts +10 -3
  200. package/dist/uploads/captureFileRef.js +18 -9
  201. package/dist/uploads/captureFileRef.js.map +1 -1
  202. package/dist/uploads/resolveFileRef.js +2 -1
  203. package/dist/uploads/resolveFileRef.js.map +1 -1
  204. package/dist/validation/builtin/email.js +1 -1
  205. package/dist/validation/builtin/email.js.map +1 -1
  206. package/dist/validation/builtin/notAlreadySubmitted.js +1 -0
  207. package/dist/validation/builtin/notAlreadySubmitted.js.map +1 -1
  208. package/dist/validation/emailPattern.js +10 -0
  209. package/dist/validation/emailPattern.js.map +1 -0
  210. package/dist/validation/registry.d.ts +2 -1
  211. package/dist/validation/registry.js +2 -9
  212. package/dist/validation/registry.js.map +1 -1
  213. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"captureFileRef.js","names":[],"sources":["../../src/uploads/captureFileRef.ts"],"sourcesContent":["import type { CollectionSlug, Payload, PayloadRequest } from 'payload'\nimport { type ResolveFileRefResult, resolveFileRef } from './resolveFileRef'\nimport type { FileFieldConfig } from './types'\n\nexport type CaptureFileRefArgs = {\n\tpayload: Payload\n\tcollectionSlug: string\n\tuploadId: string | number\n\tconfig: FileFieldConfig\n\treq?: PayloadRequest\n\t/** Resolved submitter identity; must match the upload's `owner` stamp when both are present. */\n\texpectedOwner?: string\n}\n\n/**\n * Load the referenced upload doc and run the pure trust-boundary check. The client sends only the id; the\n * filename/mimeType/filesize are read authoritatively from the stored doc, never from the client. When the\n * upload carries an `owner` stamp AND the submitter is identifiable, the two must match, so an anonymous\n * submitter cannot capture another identity's upload; a mismatch collapses to `missing`, indistinguishable\n * from a deleted upload. When the submitter cannot be identified (no `expectedOwner`), ownership is not\n * enforced (fail-open, consistent with rate-limiting): a proxy-configured deployment identifies every\n * request, so this only relaxes scoping where it could not be applied fairly anyway. Unstamped uploads\n * (no identity at upload time, or a BYO collection without the field) pass unchanged.\n */\nexport const captureFileRef = async (args: CaptureFileRefArgs): Promise<ResolveFileRefResult> => {\n\tconst { payload, collectionSlug, uploadId, config, req, expectedOwner } = args\n\tconst doc = await payload\n\t\t.findByID({\n\t\t\tcollection: collectionSlug as CollectionSlug,\n\t\t\tid: uploadId,\n\t\t\tdepth: 0,\n\t\t\toverrideAccess: true,\n\t\t\treq,\n\t\t})\n\t\t.catch(() => null)\n\tif (doc && expectedOwner != null) {\n\t\tconst owner = (doc as { owner?: unknown }).owner\n\t\tif (typeof owner === 'string' && owner.length > 0 && owner !== expectedOwner) {\n\t\t\treturn { ok: false, code: 'missing' }\n\t\t}\n\t}\n\treturn resolveFileRef(doc, config)\n}\n"],"mappings":";;;;;;;;;;;;AAwBA,MAAa,iBAAiB,OAAO,SAA4D;CAChG,MAAM,EAAE,SAAS,gBAAgB,UAAU,QAAQ,KAAK,kBAAkB;CAC1E,MAAM,MAAM,MAAM,QAChB,SAAS;EACT,YAAY;EACZ,IAAI;EACJ,OAAO;EACP,gBAAgB;EAChB;CACD,CAAC,EACA,YAAY,IAAI;CAClB,IAAI,OAAO,iBAAiB,MAAM;EACjC,MAAM,QAAS,IAA4B;EAC3C,IAAI,OAAO,UAAU,YAAY,MAAM,SAAS,KAAK,UAAU,eAC9D,OAAO;GAAE,IAAI;GAAO,MAAM;EAAU;CAEtC;CACA,OAAO,eAAe,KAAK,MAAM;AAClC"}
1
+ {"version":3,"file":"captureFileRef.js","names":[],"sources":["../../src/uploads/captureFileRef.ts"],"sourcesContent":["import type { CollectionSlug, Payload, PayloadRequest } from 'payload'\nimport { type ResolveFileRefResult, resolveFileRef } from './resolveFileRef'\nimport type { FileFieldConfig } from './types'\n\nexport type CaptureFileRefArgs = {\n\tpayload: Payload\n\tcollectionSlug: string\n\tuploadId: string | number\n\tconfig: FileFieldConfig\n\treq?: PayloadRequest\n\t/** Resolved submitter identity; must match the upload's `owner` stamp when both are present. */\n\texpectedOwner?: string\n\t/**\n\t * Fail closed when a stamped upload's submitter cannot be identified (plugin `spam.uploadOwnership:\n\t * 'strict'`). Default lenient: an unidentifiable submitter passes an owned upload through.\n\t */\n\tstrict?: boolean\n}\n\n/**\n * Load the referenced upload doc and run the pure trust-boundary check. The client sends only the id; the\n * filename/mimeType/filesize are read authoritatively from the stored doc, never from the client. When the\n * upload carries an `owner` stamp AND the submitter is identifiable, the two must match, so an anonymous\n * submitter cannot capture another identity's upload; a mismatch collapses to `missing`, indistinguishable\n * from a deleted upload. When the submitter cannot be identified (no `expectedOwner`), ownership is not\n * enforced by default (fail-open, consistent with rate-limiting): a proxy-configured deployment identifies\n * every request, so this only relaxes scoping where it could not be applied fairly anyway. Under `strict`\n * (plugin `spam.uploadOwnership: 'strict'`) a stamped upload is rejected even then, so an unverifiable\n * claim can never capture an owned upload. Unstamped uploads (no identity at upload time, or a BYO\n * collection without the field) pass unchanged in either mode.\n */\nexport const captureFileRef = async (args: CaptureFileRefArgs): Promise<ResolveFileRefResult> => {\n\tconst { payload, collectionSlug, uploadId, config, req, expectedOwner, strict } = args\n\tconst doc = await payload\n\t\t.findByID({\n\t\t\tcollection: collectionSlug as CollectionSlug,\n\t\t\tid: uploadId,\n\t\t\tdepth: 0,\n\t\t\toverrideAccess: true,\n\t\t\treq,\n\t\t})\n\t\t.catch(() => null)\n\tif (doc) {\n\t\tconst owner = (doc as { owner?: unknown }).owner\n\t\tconst stamped = typeof owner === 'string' && owner.length > 0\n\t\tif (stamped) {\n\t\t\tif (expectedOwner == null) {\n\t\t\t\tif (strict) {\n\t\t\t\t\treturn { ok: false, code: 'missing' }\n\t\t\t\t}\n\t\t\t} else if (owner !== expectedOwner) {\n\t\t\t\treturn { ok: false, code: 'missing' }\n\t\t\t}\n\t\t}\n\t}\n\treturn resolveFileRef(doc, config)\n}\n"],"mappings":";;;;;;;;;;;;;;AA+BA,MAAa,iBAAiB,OAAO,SAA4D;CAChG,MAAM,EAAE,SAAS,gBAAgB,UAAU,QAAQ,KAAK,eAAe,WAAW;CAClF,MAAM,MAAM,MAAM,QAChB,SAAS;EACT,YAAY;EACZ,IAAI;EACJ,OAAO;EACP,gBAAgB;EAChB;CACD,CAAC,EACA,YAAY,IAAI;CAClB,IAAI,KAAK;EACR,MAAM,QAAS,IAA4B;EAE3C,IADgB,OAAO,UAAU,YAAY,MAAM,SAAS;OAEvD,iBAAiB;QAChB,QACH,OAAO;KAAE,IAAI;KAAO,MAAM;IAAU;GAAA,OAE/B,IAAI,UAAU,eACpB,OAAO;IAAE,IAAI;IAAO,MAAM;GAAU;EAAA;CAGvC;CACA,OAAO,eAAe,KAAK,MAAM;AAClC"}
@@ -1,4 +1,5 @@
1
1
  //#region src/uploads/resolveFileRef.ts
2
+ /** Whether `mimeType` matches an `allow` list of exact strings or trailing-`*` prefix wildcards; empty allows all. */
2
3
  const mimeAllowed = (mimeType, allow) => {
3
4
  if (!allow || allow.length === 0) return true;
4
5
  return allow.some((pattern) => {
@@ -39,6 +40,6 @@ const resolveFileRef = (doc, config) => {
39
40
  };
40
41
  };
41
42
  //#endregion
42
- export { resolveFileRef };
43
+ export { mimeAllowed, resolveFileRef };
43
44
 
44
45
  //# sourceMappingURL=resolveFileRef.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"resolveFileRef.js","names":[],"sources":["../../src/uploads/resolveFileRef.ts"],"sourcesContent":["import type { FileFieldConfig, FileRef, FileRefError } from './types'\n\ntype UploadDoc = {\n\tid?: string | number\n\tfilename?: unknown\n\tmimeType?: unknown\n\tfilesize?: unknown\n\turl?: unknown\n}\n\nexport type ResolveFileRefResult = { ok: true; ref: FileRef } | { ok: false; code: FileRefError }\n\nconst mimeAllowed = (mimeType: string, allow: string[] | undefined): boolean => {\n\tif (!allow || allow.length === 0) {\n\t\treturn true\n\t}\n\treturn allow.some((pattern) => {\n\t\tif (pattern.endsWith('*')) {\n\t\t\treturn mimeType.startsWith(pattern.slice(0, -1))\n\t\t}\n\t\treturn mimeType === pattern\n\t})\n}\n\n/**\n * Pure server trust-boundary check for an uploaded file: given the loaded upload doc (or null) and the\n * field's constraints, either return an authoritative `FileRef` snapshot or a rejection code. Never trusts\n * client metadata; the caller passes the doc it loaded from the upload collection.\n */\nexport const resolveFileRef = (\n\tdoc: UploadDoc | null | undefined,\n\tconfig: FileFieldConfig\n): ResolveFileRefResult => {\n\tif (doc == null || doc.id == null) {\n\t\treturn { ok: false, code: 'missing' }\n\t}\n\tconst mimeType = typeof doc.mimeType === 'string' ? doc.mimeType : ''\n\tconst filesize = typeof doc.filesize === 'number' ? doc.filesize : 0\n\tif (!mimeAllowed(mimeType, config.mimeTypes)) {\n\t\treturn { ok: false, code: 'mimeType' }\n\t}\n\tif (typeof config.maxSize === 'number' && filesize > config.maxSize) {\n\t\treturn { ok: false, code: 'tooLarge' }\n\t}\n\tconst ref: FileRef = {\n\t\tid: doc.id,\n\t\tfilename: typeof doc.filename === 'string' ? doc.filename : '',\n\t\tmimeType,\n\t\tfilesize,\n\t}\n\tif (typeof doc.url === 'string' && doc.url.length > 0) {\n\t\tref.url = doc.url\n\t}\n\treturn { ok: true, ref }\n}\n"],"mappings":";AAYA,MAAM,eAAe,UAAkB,UAAyC;CAC/E,IAAI,CAAC,SAAS,MAAM,WAAW,GAC9B,OAAO;CAER,OAAO,MAAM,MAAM,YAAY;EAC9B,IAAI,QAAQ,SAAS,GAAG,GACvB,OAAO,SAAS,WAAW,QAAQ,MAAM,GAAG,EAAE,CAAC;EAEhD,OAAO,aAAa;CACrB,CAAC;AACF;;;;;;AAOA,MAAa,kBACZ,KACA,WAC0B;CAC1B,IAAI,OAAO,QAAQ,IAAI,MAAM,MAC5B,OAAO;EAAE,IAAI;EAAO,MAAM;CAAU;CAErC,MAAM,WAAW,OAAO,IAAI,aAAa,WAAW,IAAI,WAAW;CACnE,MAAM,WAAW,OAAO,IAAI,aAAa,WAAW,IAAI,WAAW;CACnE,IAAI,CAAC,YAAY,UAAU,OAAO,SAAS,GAC1C,OAAO;EAAE,IAAI;EAAO,MAAM;CAAW;CAEtC,IAAI,OAAO,OAAO,YAAY,YAAY,WAAW,OAAO,SAC3D,OAAO;EAAE,IAAI;EAAO,MAAM;CAAW;CAEtC,MAAM,MAAe;EACpB,IAAI,IAAI;EACR,UAAU,OAAO,IAAI,aAAa,WAAW,IAAI,WAAW;EAC5D;EACA;CACD;CACA,IAAI,OAAO,IAAI,QAAQ,YAAY,IAAI,IAAI,SAAS,GACnD,IAAI,MAAM,IAAI;CAEf,OAAO;EAAE,IAAI;EAAM;CAAI;AACxB"}
1
+ {"version":3,"file":"resolveFileRef.js","names":[],"sources":["../../src/uploads/resolveFileRef.ts"],"sourcesContent":["import type { FileFieldConfig, FileRef, FileRefError } from './types'\n\ntype UploadDoc = {\n\tid?: string | number\n\tfilename?: unknown\n\tmimeType?: unknown\n\tfilesize?: unknown\n\turl?: unknown\n}\n\nexport type ResolveFileRefResult = { ok: true; ref: FileRef } | { ok: false; code: FileRefError }\n\n/** Whether `mimeType` matches an `allow` list of exact strings or trailing-`*` prefix wildcards; empty allows all. */\nexport const mimeAllowed = (mimeType: string, allow: string[] | undefined): boolean => {\n\tif (!allow || allow.length === 0) {\n\t\treturn true\n\t}\n\treturn allow.some((pattern) => {\n\t\tif (pattern.endsWith('*')) {\n\t\t\treturn mimeType.startsWith(pattern.slice(0, -1))\n\t\t}\n\t\treturn mimeType === pattern\n\t})\n}\n\n/**\n * Pure server trust-boundary check for an uploaded file: given the loaded upload doc (or null) and the\n * field's constraints, either return an authoritative `FileRef` snapshot or a rejection code. Never trusts\n * client metadata; the caller passes the doc it loaded from the upload collection.\n */\nexport const resolveFileRef = (\n\tdoc: UploadDoc | null | undefined,\n\tconfig: FileFieldConfig\n): ResolveFileRefResult => {\n\tif (doc == null || doc.id == null) {\n\t\treturn { ok: false, code: 'missing' }\n\t}\n\tconst mimeType = typeof doc.mimeType === 'string' ? doc.mimeType : ''\n\tconst filesize = typeof doc.filesize === 'number' ? doc.filesize : 0\n\tif (!mimeAllowed(mimeType, config.mimeTypes)) {\n\t\treturn { ok: false, code: 'mimeType' }\n\t}\n\tif (typeof config.maxSize === 'number' && filesize > config.maxSize) {\n\t\treturn { ok: false, code: 'tooLarge' }\n\t}\n\tconst ref: FileRef = {\n\t\tid: doc.id,\n\t\tfilename: typeof doc.filename === 'string' ? doc.filename : '',\n\t\tmimeType,\n\t\tfilesize,\n\t}\n\tif (typeof doc.url === 'string' && doc.url.length > 0) {\n\t\tref.url = doc.url\n\t}\n\treturn { ok: true, ref }\n}\n"],"mappings":";;AAaA,MAAa,eAAe,UAAkB,UAAyC;CACtF,IAAI,CAAC,SAAS,MAAM,WAAW,GAC9B,OAAO;CAER,OAAO,MAAM,MAAM,YAAY;EAC9B,IAAI,QAAQ,SAAS,GAAG,GACvB,OAAO,SAAS,WAAW,QAAQ,MAAM,GAAG,EAAE,CAAC;EAEhD,OAAO,aAAa;CACrB,CAAC;AACF;;;;;;AAOA,MAAa,kBACZ,KACA,WAC0B;CAC1B,IAAI,OAAO,QAAQ,IAAI,MAAM,MAC5B,OAAO;EAAE,IAAI;EAAO,MAAM;CAAU;CAErC,MAAM,WAAW,OAAO,IAAI,aAAa,WAAW,IAAI,WAAW;CACnE,MAAM,WAAW,OAAO,IAAI,aAAa,WAAW,IAAI,WAAW;CACnE,IAAI,CAAC,YAAY,UAAU,OAAO,SAAS,GAC1C,OAAO;EAAE,IAAI;EAAO,MAAM;CAAW;CAEtC,IAAI,OAAO,OAAO,YAAY,YAAY,WAAW,OAAO,SAC3D,OAAO;EAAE,IAAI;EAAO,MAAM;CAAW;CAEtC,MAAM,MAAe;EACpB,IAAI,IAAI;EACR,UAAU,OAAO,IAAI,aAAa,WAAW,IAAI,WAAW;EAC5D;EACA;CACD;CACA,IAAI,OAAO,IAAI,QAAQ,YAAY,IAAI,IAAI,SAAS,GACnD,IAAI,MAAM,IAAI;CAEf,OAAO;EAAE,IAAI;EAAM;CAAI;AACxB"}
@@ -1,7 +1,7 @@
1
1
  import { keys } from "../../translations/keys.js";
2
+ import { EMAIL_PATTERN } from "../emailPattern.js";
2
3
  import { defineValidationRule } from "../defineValidationRule.js";
3
4
  //#region src/validation/builtin/email.ts
4
- const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
5
5
  const emailRule = defineValidationRule({
6
6
  type: "email",
7
7
  label: keys.ruleEmail,
@@ -1 +1 @@
1
- {"version":3,"file":"email.js","names":[],"sources":["../../../src/validation/builtin/email.ts"],"sourcesContent":["import { keys } from '../../translations/keys'\nimport { defineValidationRule } from '../defineValidationRule'\n\nconst EMAIL_PATTERN = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\n\nexport const emailRule = defineValidationRule<Record<string, never>, string>({\n\ttype: 'email',\n\tlabel: keys.ruleEmail,\n\tdescription: keys.ruleEmailDescription,\n\tappliesTo: ['text', 'textarea', 'email'],\n\tdefaultMessage: keys.ruleEmailMessage,\n\tvalidate: ({ value, message }) =>\n\t\tvalue == null || value === '' || EMAIL_PATTERN.test(value) ? true : message(),\n})\n"],"mappings":";;;AAGA,MAAM,gBAAgB;AAEtB,MAAa,YAAY,qBAAoD;CAC5E,MAAM;CACN,OAAO,KAAK;CACZ,aAAa,KAAK;CAClB,WAAW;EAAC;EAAQ;EAAY;CAAO;CACvC,gBAAgB,KAAK;CACrB,WAAW,EAAE,OAAO,cACnB,SAAS,QAAQ,UAAU,MAAM,cAAc,KAAK,KAAK,IAAI,OAAO,QAAQ;AAC9E,CAAC"}
1
+ {"version":3,"file":"email.js","names":[],"sources":["../../../src/validation/builtin/email.ts"],"sourcesContent":["import { keys } from '../../translations/keys'\nimport { defineValidationRule } from '../defineValidationRule'\nimport { EMAIL_PATTERN } from '../emailPattern'\n\nexport const emailRule = defineValidationRule<Record<string, never>, string>({\n\ttype: 'email',\n\tlabel: keys.ruleEmail,\n\tdescription: keys.ruleEmailDescription,\n\tappliesTo: ['text', 'textarea', 'email'],\n\tdefaultMessage: keys.ruleEmailMessage,\n\tvalidate: ({ value, message }) =>\n\t\tvalue == null || value === '' || EMAIL_PATTERN.test(value) ? true : message(),\n})\n"],"mappings":";;;;AAIA,MAAa,YAAY,qBAAoD;CAC5E,MAAM;CACN,OAAO,KAAK;CACZ,aAAa,KAAK;CAClB,WAAW;EAAC;EAAQ;EAAY;CAAO;CACvC,gBAAgB,KAAK;CACrB,WAAW,EAAE,OAAO,cACnB,SAAS,QAAQ,UAAU,MAAM,cAAc,KAAK,KAAK,IAAI,OAAO,QAAQ;AAC9E,CAAC"}
@@ -21,6 +21,7 @@ const notAlreadySubmittedRule = defineValidationRule({
21
21
  where: { form: { equals: formId } },
22
22
  limit: SCAN_LIMIT,
23
23
  depth: 0,
24
+ overrideAccess: true,
24
25
  req
25
26
  })).docs.some((doc) => {
26
27
  return (doc.values ?? []).some((entry) => entry.field === field.name && entry.value === value);
@@ -1 +1 @@
1
- {"version":3,"file":"notAlreadySubmitted.js","names":[],"sources":["../../../src/validation/builtin/notAlreadySubmitted.ts"],"sourcesContent":["import { keys } from '../../translations/keys'\nimport { defineValidationRule } from '../defineValidationRule'\n\nconst FORM_SUBMISSIONS_SLUG = 'form-submissions'\nconst SCAN_LIMIT = 500\n\n/**\n * Server-only async exemplar: rejects a value already present for the same form. Scans up to\n * `SCAN_LIMIT` recent submissions in JS (the values live in a json column, so a portable DB query is\n * not available); a future phase can add an indexed dedup column for scale.\n */\nexport const notAlreadySubmittedRule = defineValidationRule<Record<string, never>, unknown>({\n\ttype: 'notAlreadySubmitted',\n\tlabel: keys.ruleNotAlreadySubmitted,\n\tdescription: keys.ruleNotAlreadySubmittedDescription,\n\tclient: false,\n\tdefaultMessage: keys.ruleNotAlreadySubmittedMessage,\n\tvalidate: async ({ value, field, payload, req, formId, message }) => {\n\t\tif (value == null || value === '' || !payload || formId == null) {\n\t\t\treturn true\n\t\t}\n\t\tconst result = await payload.find({\n\t\t\tcollection: FORM_SUBMISSIONS_SLUG,\n\t\t\twhere: { form: { equals: formId } },\n\t\t\tlimit: SCAN_LIMIT,\n\t\t\tdepth: 0,\n\t\t\treq,\n\t\t})\n\t\tconst clash = result.docs.some((doc) => {\n\t\t\tconst values = (doc as { values?: { field: string; value: unknown }[] }).values ?? []\n\t\t\treturn values.some((entry) => entry.field === field.name && entry.value === value)\n\t\t})\n\t\treturn clash ? message() : true\n\t},\n})\n"],"mappings":";;;AAGA,MAAM,wBAAwB;AAC9B,MAAM,aAAa;;;;;;AAOnB,MAAa,0BAA0B,qBAAqD;CAC3F,MAAM;CACN,OAAO,KAAK;CACZ,aAAa,KAAK;CAClB,QAAQ;CACR,gBAAgB,KAAK;CACrB,UAAU,OAAO,EAAE,OAAO,OAAO,SAAS,KAAK,QAAQ,cAAc;EACpE,IAAI,SAAS,QAAQ,UAAU,MAAM,CAAC,WAAW,UAAU,MAC1D,OAAO;EAaR,QAJc,MAPO,QAAQ,KAAK;GACjC,YAAY;GACZ,OAAO,EAAE,MAAM,EAAE,QAAQ,OAAO,EAAE;GAClC,OAAO;GACP,OAAO;GACP;EACD,CAAC,GACoB,KAAK,MAAM,QAAQ;GAEvC,QADgB,IAAyD,UAAU,CAAC,GACtE,MAAM,UAAU,MAAM,UAAU,MAAM,QAAQ,MAAM,UAAU,KAAK;EAClF,CACW,IAAI,QAAQ,IAAI;CAC5B;AACD,CAAC"}
1
+ {"version":3,"file":"notAlreadySubmitted.js","names":[],"sources":["../../../src/validation/builtin/notAlreadySubmitted.ts"],"sourcesContent":["import { keys } from '../../translations/keys'\nimport { defineValidationRule } from '../defineValidationRule'\n\nconst FORM_SUBMISSIONS_SLUG = 'form-submissions'\nconst SCAN_LIMIT = 500\n\n/**\n * Server-only async exemplar: rejects a value already present for the same form. Scans up to\n * `SCAN_LIMIT` recent submissions in JS (the values live in a json column, so a portable DB query is\n * not available); a future phase can add an indexed dedup column for scale.\n */\nexport const notAlreadySubmittedRule = defineValidationRule<Record<string, never>, unknown>({\n\ttype: 'notAlreadySubmitted',\n\tlabel: keys.ruleNotAlreadySubmitted,\n\tdescription: keys.ruleNotAlreadySubmittedDescription,\n\tclient: false,\n\tdefaultMessage: keys.ruleNotAlreadySubmittedMessage,\n\tvalidate: async ({ value, field, payload, req, formId, message }) => {\n\t\tif (value == null || value === '' || !payload || formId == null) {\n\t\t\treturn true\n\t\t}\n\t\tconst result = await payload.find({\n\t\t\tcollection: FORM_SUBMISSIONS_SLUG,\n\t\t\twhere: { form: { equals: formId } },\n\t\t\tlimit: SCAN_LIMIT,\n\t\t\tdepth: 0,\n\t\t\t// Trusted internal dedup read: scan every prior submission of this form regardless of the\n\t\t\t// submitter's own access. This is the local-API default made explicit, matching the sibling\n\t\t\t// resolvePollOutcome/aggregateResponses reads; the `form` filter keeps it single-tenant.\n\t\t\toverrideAccess: true,\n\t\t\treq,\n\t\t})\n\t\tconst clash = result.docs.some((doc) => {\n\t\t\tconst values = (doc as { values?: { field: string; value: unknown }[] }).values ?? []\n\t\t\treturn values.some((entry) => entry.field === field.name && entry.value === value)\n\t\t})\n\t\treturn clash ? message() : true\n\t},\n})\n"],"mappings":";;;AAGA,MAAM,wBAAwB;AAC9B,MAAM,aAAa;;;;;;AAOnB,MAAa,0BAA0B,qBAAqD;CAC3F,MAAM;CACN,OAAO,KAAK;CACZ,aAAa,KAAK;CAClB,QAAQ;CACR,gBAAgB,KAAK;CACrB,UAAU,OAAO,EAAE,OAAO,OAAO,SAAS,KAAK,QAAQ,cAAc;EACpE,IAAI,SAAS,QAAQ,UAAU,MAAM,CAAC,WAAW,UAAU,MAC1D,OAAO;EAiBR,QAJc,MAXO,QAAQ,KAAK;GACjC,YAAY;GACZ,OAAO,EAAE,MAAM,EAAE,QAAQ,OAAO,EAAE;GAClC,OAAO;GACP,OAAO;GAIP,gBAAgB;GAChB;EACD,CAAC,GACoB,KAAK,MAAM,QAAQ;GAEvC,QADgB,IAAyD,UAAU,CAAC,GACtE,MAAM,UAAU,MAAM,UAAU,MAAM,QAAQ,MAAM,UAAU,KAAK;EAClF,CACW,IAAI,QAAQ,IAAI;CAC5B;AACD,CAAC"}
@@ -0,0 +1,10 @@
1
+ //#region src/validation/emailPattern.ts
2
+ /**
3
+ * The email-format check shared by the `email` field type and the `email` validation rule, so the
4
+ * two never drift. A pragmatic "one @, one dot, no spaces" test, not a full RFC 5322 grammar.
5
+ */
6
+ const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
7
+ //#endregion
8
+ export { EMAIL_PATTERN };
9
+
10
+ //# sourceMappingURL=emailPattern.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emailPattern.js","names":[],"sources":["../../src/validation/emailPattern.ts"],"sourcesContent":["/**\n * The email-format check shared by the `email` field type and the `email` validation rule, so the\n * two never drift. A pragmatic \"one @, one dot, no spaces\" test, not a full RFC 5322 grammar.\n */\nexport const EMAIL_PATTERN = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\n"],"mappings":";;;;;AAIA,MAAa,gBAAgB"}
@@ -1,10 +1,11 @@
1
+ import { RegistryConfig } from "../plugin/applyRegistryConfig.js";
1
2
  import { AnyValidationRuleDefinition } from "./types.js";
2
3
 
3
4
  //#region src/validation/registry.d.ts
4
5
  type ValidationRuleRegistry = Map<string, AnyValidationRuleDefinition>;
5
6
  /** Per-rule opt-in: `false` removes a built-in, `true` keeps it, an object adds a new rule or replaces one. */
6
7
  type ValidationRuleOption = boolean | AnyValidationRuleDefinition;
7
- type ValidationRulesConfig = Record<string, ValidationRuleOption>;
8
+ type ValidationRulesConfig = RegistryConfig<AnyValidationRuleDefinition>;
8
9
  //#endregion
9
10
  export { ValidationRuleOption, ValidationRuleRegistry, ValidationRulesConfig };
10
11
  //# sourceMappingURL=registry.d.ts.map
@@ -1,3 +1,4 @@
1
+ import { applyRegistryConfig } from "../plugin/applyRegistryConfig.js";
1
2
  //#region src/validation/registry.ts
2
3
  const buildRuleRegistry = (rules) => {
3
4
  const registry = /* @__PURE__ */ new Map();
@@ -5,15 +6,7 @@ const buildRuleRegistry = (rules) => {
5
6
  return registry;
6
7
  };
7
8
  /** Resolve the active rule registry from the built-in defaults and the plugin `rules` option. */
8
- const resolveValidationRules = (defaults, config = {}) => {
9
- const registry = buildRuleRegistry(defaults);
10
- for (const [type, option] of Object.entries(config)) if (option === false) registry.delete(type);
11
- else if (option === true) {} else registry.set(type, {
12
- ...option,
13
- type
14
- });
15
- return registry;
16
- };
9
+ const resolveValidationRules = (defaults, config = {}) => applyRegistryConfig(buildRuleRegistry(defaults), config);
17
10
  //#endregion
18
11
  export { buildRuleRegistry, resolveValidationRules };
19
12
 
@@ -1 +1 @@
1
- {"version":3,"file":"registry.js","names":[],"sources":["../../src/validation/registry.ts"],"sourcesContent":["import type { AnyValidationRuleDefinition } from './types'\n\nexport type ValidationRuleRegistry = Map<string, AnyValidationRuleDefinition>\n\n/** Per-rule opt-in: `false` removes a built-in, `true` keeps it, an object adds a new rule or replaces one. */\nexport type ValidationRuleOption = boolean | AnyValidationRuleDefinition\n\nexport type ValidationRulesConfig = Record<string, ValidationRuleOption>\n\nexport const buildRuleRegistry = (rules: AnyValidationRuleDefinition[]): ValidationRuleRegistry => {\n\tconst registry: ValidationRuleRegistry = new Map()\n\tfor (const rule of rules) {\n\t\tregistry.set(rule.type, rule)\n\t}\n\treturn registry\n}\n\n/** Resolve the active rule registry from the built-in defaults and the plugin `rules` option. */\nexport const resolveValidationRules = (\n\tdefaults: AnyValidationRuleDefinition[],\n\tconfig: ValidationRulesConfig = {}\n): ValidationRuleRegistry => {\n\tconst registry = buildRuleRegistry(defaults)\n\tfor (const [type, option] of Object.entries(config)) {\n\t\tif (option === false) {\n\t\t\tregistry.delete(type)\n\t\t} else if (option === true) {\n\t\t\t// keep the default; a no-op when no default exists for this key\n\t\t} else {\n\t\t\tregistry.set(type, { ...option, type })\n\t\t}\n\t}\n\treturn registry\n}\n"],"mappings":";AASA,MAAa,qBAAqB,UAAiE;CAClG,MAAM,2BAAmC,IAAI,IAAI;CACjD,KAAK,MAAM,QAAQ,OAClB,SAAS,IAAI,KAAK,MAAM,IAAI;CAE7B,OAAO;AACR;;AAGA,MAAa,0BACZ,UACA,SAAgC,CAAC,MACL;CAC5B,MAAM,WAAW,kBAAkB,QAAQ;CAC3C,KAAK,MAAM,CAAC,MAAM,WAAW,OAAO,QAAQ,MAAM,GACjD,IAAI,WAAW,OACd,SAAS,OAAO,IAAI;MACd,IAAI,WAAW,MAAM,CAE5B,OACC,SAAS,IAAI,MAAM;EAAE,GAAG;EAAQ;CAAK,CAAC;CAGxC,OAAO;AACR"}
1
+ {"version":3,"file":"registry.js","names":[],"sources":["../../src/validation/registry.ts"],"sourcesContent":["import { applyRegistryConfig, type RegistryConfig } from '../plugin/applyRegistryConfig'\nimport type { AnyValidationRuleDefinition } from './types'\n\nexport type ValidationRuleRegistry = Map<string, AnyValidationRuleDefinition>\n\n/** Per-rule opt-in: `false` removes a built-in, `true` keeps it, an object adds a new rule or replaces one. */\nexport type ValidationRuleOption = boolean | AnyValidationRuleDefinition\n\nexport type ValidationRulesConfig = RegistryConfig<AnyValidationRuleDefinition>\n\nexport const buildRuleRegistry = (rules: AnyValidationRuleDefinition[]): ValidationRuleRegistry => {\n\tconst registry: ValidationRuleRegistry = new Map()\n\tfor (const rule of rules) {\n\t\tregistry.set(rule.type, rule)\n\t}\n\treturn registry\n}\n\n/** Resolve the active rule registry from the built-in defaults and the plugin `rules` option. */\nexport const resolveValidationRules = (\n\tdefaults: AnyValidationRuleDefinition[],\n\tconfig: ValidationRulesConfig = {}\n): ValidationRuleRegistry => applyRegistryConfig(buildRuleRegistry(defaults), config)\n"],"mappings":";;AAUA,MAAa,qBAAqB,UAAiE;CAClG,MAAM,2BAAmC,IAAI,IAAI;CACjD,KAAK,MAAM,QAAQ,OAClB,SAAS,IAAI,KAAK,MAAM,IAAI;CAE7B,OAAO;AACR;;AAGA,MAAa,0BACZ,UACA,SAAgC,CAAC,MACL,oBAAoB,kBAAkB,QAAQ,GAAG,MAAM"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@10x-media/form-builder",
3
- "version": "0.1.0-beta.5",
3
+ "version": "0.1.0-beta.7",
4
4
  "description": "End-to-end forms platform for Payload: author, validate, render, collect, aggregate, and act.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -94,8 +94,8 @@
94
94
  "typescript": "5.9.3",
95
95
  "vitest": "4.1.7",
96
96
  "@10x-media/payload-test-harness": "0.0.0",
97
- "@10x-media/tsconfig": "0.0.0",
98
97
  "@10x-media/tsdown-config": "0.0.0",
98
+ "@10x-media/tsconfig": "0.0.0",
99
99
  "@10x-media/vitest-config": "0.0.0"
100
100
  },
101
101
  "publishConfig": {