payload-dynamic-forms 0.0.0-stage → 0.1.0

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 (99) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +404 -2
  3. package/dist/access.d.ts +7 -0
  4. package/dist/access.js +10 -0
  5. package/dist/access.js.map +1 -0
  6. package/dist/collections/attachments.d.ts +6 -0
  7. package/dist/collections/attachments.js +67 -0
  8. package/dist/collections/attachments.js.map +1 -0
  9. package/dist/collections/blocks.d.ts +27 -0
  10. package/dist/collections/blocks.js +495 -0
  11. package/dist/collections/blocks.js.map +1 -0
  12. package/dist/collections/forms.d.ts +3 -0
  13. package/dist/collections/forms.js +553 -0
  14. package/dist/collections/forms.js.map +1 -0
  15. package/dist/collections/overrides.d.ts +7 -0
  16. package/dist/collections/overrides.js +49 -0
  17. package/dist/collections/overrides.js.map +1 -0
  18. package/dist/collections/ruleFields.d.ts +6 -0
  19. package/dist/collections/ruleFields.js +97 -0
  20. package/dist/collections/ruleFields.js.map +1 -0
  21. package/dist/collections/submissions.d.ts +7 -0
  22. package/dist/collections/submissions.js +225 -0
  23. package/dist/collections/submissions.js.map +1 -0
  24. package/dist/delivery/run.d.ts +36 -0
  25. package/dist/delivery/run.js +187 -0
  26. package/dist/delivery/run.js.map +1 -0
  27. package/dist/delivery/webhook.d.ts +26 -0
  28. package/dist/delivery/webhook.js +71 -0
  29. package/dist/delivery/webhook.js.map +1 -0
  30. package/dist/endpoints/public.d.ts +4 -0
  31. package/dist/endpoints/public.js +27 -0
  32. package/dist/endpoints/public.js.map +1 -0
  33. package/dist/endpoints/resend.d.ts +4 -0
  34. package/dist/endpoints/resend.js +58 -0
  35. package/dist/endpoints/resend.js.map +1 -0
  36. package/dist/endpoints/submit.d.ts +4 -0
  37. package/dist/endpoints/submit.js +320 -0
  38. package/dist/endpoints/submit.js.map +1 -0
  39. package/dist/exports/shared.d.ts +13 -0
  40. package/dist/exports/shared.js +13 -0
  41. package/dist/exports/shared.js.map +1 -0
  42. package/dist/formBlock.d.ts +13 -0
  43. package/dist/formBlock.js +25 -0
  44. package/dist/formBlock.js.map +1 -0
  45. package/dist/hooks/resendTick.d.ts +8 -0
  46. package/dist/hooks/resendTick.js +55 -0
  47. package/dist/hooks/resendTick.js.map +1 -0
  48. package/dist/hooks/validateForm.d.ts +8 -0
  49. package/dist/hooks/validateForm.js +251 -0
  50. package/dist/hooks/validateForm.js.map +1 -0
  51. package/dist/index.d.ts +18 -0
  52. package/dist/index.js +124 -0
  53. package/dist/index.js.map +1 -0
  54. package/dist/security/crypto.d.ts +2 -0
  55. package/dist/security/crypto.js +17 -0
  56. package/dist/security/crypto.js.map +1 -0
  57. package/dist/security/rateLimit.d.ts +13 -0
  58. package/dist/security/rateLimit.js +28 -0
  59. package/dist/security/rateLimit.js.map +1 -0
  60. package/dist/security/request.d.ts +3 -0
  61. package/dist/security/request.js +13 -0
  62. package/dist/security/request.js.map +1 -0
  63. package/dist/security/turnstile.d.ts +28 -0
  64. package/dist/security/turnstile.js +56 -0
  65. package/dist/security/turnstile.js.map +1 -0
  66. package/dist/server/forms.d.ts +73 -0
  67. package/dist/server/forms.js +215 -0
  68. package/dist/server/forms.js.map +1 -0
  69. package/dist/server/resend.d.ts +27 -0
  70. package/dist/server/resend.js +84 -0
  71. package/dist/server/resend.js.map +1 -0
  72. package/dist/shared/answers.d.ts +47 -0
  73. package/dist/shared/answers.js +282 -0
  74. package/dist/shared/answers.js.map +1 -0
  75. package/dist/shared/operators.d.ts +41 -0
  76. package/dist/shared/operators.js +54 -0
  77. package/dist/shared/operators.js.map +1 -0
  78. package/dist/shared/rules.d.ts +58 -0
  79. package/dist/shared/rules.js +324 -0
  80. package/dist/shared/rules.js.map +1 -0
  81. package/dist/shared/steps.d.ts +28 -0
  82. package/dist/shared/steps.js +109 -0
  83. package/dist/shared/steps.js.map +1 -0
  84. package/dist/shared/tokens.d.ts +25 -0
  85. package/dist/shared/tokens.js +96 -0
  86. package/dist/shared/tokens.js.map +1 -0
  87. package/dist/shared/types.d.ts +181 -0
  88. package/dist/shared/types.js +5 -0
  89. package/dist/shared/types.js.map +1 -0
  90. package/dist/shared/whatsapp.d.ts +20 -0
  91. package/dist/shared/whatsapp.js +26 -0
  92. package/dist/shared/whatsapp.js.map +1 -0
  93. package/dist/shared/words.d.ts +9 -0
  94. package/dist/shared/words.js +41 -0
  95. package/dist/shared/words.js.map +1 -0
  96. package/dist/types.d.ts +133 -0
  97. package/dist/types.js +3 -0
  98. package/dist/types.js.map +1 -0
  99. package/package.json +113 -4
@@ -0,0 +1,71 @@
1
+ import { hmacSha256Hex } from '../security/crypto.js';
2
+ /** Pauses between tries: 0.5 s, 1 s, 2 s. */ export const RETRY_PAUSES_MS = [
3
+ 500,
4
+ 1000,
5
+ 2000
6
+ ];
7
+ const sleep = (ms)=>new Promise((resolve)=>setTimeout(resolve, ms));
8
+ /** `X-Form-Signature` value: `t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<body>">`. */ export async function signWebhookBody(secret, body, now = Date.now()) {
9
+ const t = Math.floor(now / 1000);
10
+ return `t=${t},v1=${await hmacSha256Hex(secret, `${t}.${body}`)}`;
11
+ }
12
+ /**
13
+ * Sends one webhook: JSON body, secret headers, optional signature, a timeout per try (default
14
+ * 8 s) and up to 3 retries with growing pauses. A non-2xx answer or a timeout is a failed try.
15
+ */ export async function sendWebhook(settings, body) {
16
+ const json = JSON.stringify(body);
17
+ const timeoutMs = Math.min(30_000, Math.max(500, Number(settings.timeoutMs) || 8000));
18
+ const retries = Math.min(3, Math.max(0, Math.floor(Number(settings.retries) || 0)));
19
+ let lastError = '';
20
+ let lastStatus;
21
+ let tries = 0;
22
+ for(let attempt = 0; attempt <= retries; attempt += 1){
23
+ if (attempt > 0) {
24
+ await sleep(RETRY_PAUSES_MS[Math.min(attempt - 1, RETRY_PAUSES_MS.length - 1)]);
25
+ }
26
+ tries += 1;
27
+ const headers = {
28
+ 'Content-Type': 'application/json'
29
+ };
30
+ for (const header of settings.headers || []){
31
+ if (header?.key && header.value) {
32
+ headers[header.key] = header.value;
33
+ }
34
+ }
35
+ if (settings.signingSecret) {
36
+ headers['X-Form-Signature'] = await signWebhookBody(settings.signingSecret, json);
37
+ }
38
+ const controller = new AbortController();
39
+ const timer = setTimeout(()=>controller.abort(), timeoutMs);
40
+ try {
41
+ const response = await fetch(String(settings.url || ''), {
42
+ body: json,
43
+ headers,
44
+ method: settings.method || 'POST',
45
+ signal: controller.signal
46
+ });
47
+ lastStatus = response.status;
48
+ if (response.ok) {
49
+ return {
50
+ ok: true,
51
+ status: response.status,
52
+ tries
53
+ };
54
+ }
55
+ lastError = `Webhook answered ${response.status}.`;
56
+ } catch (error) {
57
+ lastStatus = undefined;
58
+ lastError = controller.signal.aborted ? `Webhook timed out after ${timeoutMs} ms.` : error instanceof Error ? error.message : String(error);
59
+ } finally{
60
+ clearTimeout(timer);
61
+ }
62
+ }
63
+ return {
64
+ error: lastError,
65
+ ok: false,
66
+ status: lastStatus,
67
+ tries
68
+ };
69
+ }
70
+
71
+ //# sourceMappingURL=webhook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/delivery/webhook.ts"],"sourcesContent":["import { hmacSha256Hex } from '../security/crypto.js'\n\nexport type WebhookSettings = {\n headers?: Array<{ key?: null | string; value?: null | string }> | null\n method?: 'PATCH' | 'POST' | 'PUT' | null\n retries?: null | number\n signingSecret?: null | string\n timeoutMs?: null | number\n url?: null | string\n}\n\nexport type WebhookResult = { error?: string; ok: boolean; status?: number; tries: number }\n\n/** Pauses between tries: 0.5 s, 1 s, 2 s. */\nexport const RETRY_PAUSES_MS = [500, 1000, 2000]\n\nconst sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms))\n\n/** `X-Form-Signature` value: `t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<body>\">`. */\nexport async function signWebhookBody(secret: string, body: string, now = Date.now()): Promise<string> {\n const t = Math.floor(now / 1000)\n return `t=${t},v1=${await hmacSha256Hex(secret, `${t}.${body}`)}`\n}\n\n/**\n * Sends one webhook: JSON body, secret headers, optional signature, a timeout per try (default\n * 8 s) and up to 3 retries with growing pauses. A non-2xx answer or a timeout is a failed try.\n */\nexport async function sendWebhook(settings: WebhookSettings, body: unknown): Promise<WebhookResult> {\n const json = JSON.stringify(body)\n const timeoutMs = Math.min(30_000, Math.max(500, Number(settings.timeoutMs) || 8000))\n const retries = Math.min(3, Math.max(0, Math.floor(Number(settings.retries) || 0)))\n let lastError = ''\n let lastStatus: number | undefined\n let tries = 0\n for (let attempt = 0; attempt <= retries; attempt += 1) {\n if (attempt > 0) {\n await sleep(RETRY_PAUSES_MS[Math.min(attempt - 1, RETRY_PAUSES_MS.length - 1)])\n }\n tries += 1\n const headers: Record<string, string> = { 'Content-Type': 'application/json' }\n for (const header of settings.headers || []) {\n if (header?.key && header.value) {\n headers[header.key] = header.value\n }\n }\n if (settings.signingSecret) {\n headers['X-Form-Signature'] = await signWebhookBody(settings.signingSecret, json)\n }\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), timeoutMs)\n try {\n const response = await fetch(String(settings.url || ''), {\n body: json,\n headers,\n method: settings.method || 'POST',\n signal: controller.signal,\n })\n lastStatus = response.status\n if (response.ok) {\n return { ok: true, status: response.status, tries }\n }\n lastError = `Webhook answered ${response.status}.`\n } catch (error) {\n lastStatus = undefined\n lastError = controller.signal.aborted\n ? `Webhook timed out after ${timeoutMs} ms.`\n : error instanceof Error\n ? error.message\n : String(error)\n } finally {\n clearTimeout(timer)\n }\n }\n return { error: lastError, ok: false, status: lastStatus, tries }\n}\n"],"names":["hmacSha256Hex","RETRY_PAUSES_MS","sleep","ms","Promise","resolve","setTimeout","signWebhookBody","secret","body","now","Date","t","Math","floor","sendWebhook","settings","json","JSON","stringify","timeoutMs","min","max","Number","retries","lastError","lastStatus","tries","attempt","length","headers","header","key","value","signingSecret","controller","AbortController","timer","abort","response","fetch","String","url","method","signal","status","ok","error","undefined","aborted","Error","message","clearTimeout"],"mappings":"AAAA,SAASA,aAAa,QAAQ,wBAAuB;AAarD,2CAA2C,GAC3C,OAAO,MAAMC,kBAAkB;IAAC;IAAK;IAAM;CAAK,CAAA;AAEhD,MAAMC,QAAQ,CAACC,KAAe,IAAIC,QAAc,CAACC,UAAYC,WAAWD,SAASF;AAEjF,uFAAuF,GACvF,OAAO,eAAeI,gBAAgBC,MAAc,EAAEC,IAAY,EAAEC,MAAMC,KAAKD,GAAG,EAAE;IAClF,MAAME,IAAIC,KAAKC,KAAK,CAACJ,MAAM;IAC3B,OAAO,CAAC,EAAE,EAAEE,EAAE,IAAI,EAAE,MAAMZ,cAAcQ,QAAQ,GAAGI,EAAE,CAAC,EAAEH,MAAM,GAAG;AACnE;AAEA;;;CAGC,GACD,OAAO,eAAeM,YAAYC,QAAyB,EAAEP,IAAa;IACxE,MAAMQ,OAAOC,KAAKC,SAAS,CAACV;IAC5B,MAAMW,YAAYP,KAAKQ,GAAG,CAAC,QAAQR,KAAKS,GAAG,CAAC,KAAKC,OAAOP,SAASI,SAAS,KAAK;IAC/E,MAAMI,UAAUX,KAAKQ,GAAG,CAAC,GAAGR,KAAKS,GAAG,CAAC,GAAGT,KAAKC,KAAK,CAACS,OAAOP,SAASQ,OAAO,KAAK;IAC/E,IAAIC,YAAY;IAChB,IAAIC;IACJ,IAAIC,QAAQ;IACZ,IAAK,IAAIC,UAAU,GAAGA,WAAWJ,SAASI,WAAW,EAAG;QACtD,IAAIA,UAAU,GAAG;YACf,MAAM1B,MAAMD,eAAe,CAACY,KAAKQ,GAAG,CAACO,UAAU,GAAG3B,gBAAgB4B,MAAM,GAAG,GAAG;QAChF;QACAF,SAAS;QACT,MAAMG,UAAkC;YAAE,gBAAgB;QAAmB;QAC7E,KAAK,MAAMC,UAAUf,SAASc,OAAO,IAAI,EAAE,CAAE;YAC3C,IAAIC,QAAQC,OAAOD,OAAOE,KAAK,EAAE;gBAC/BH,OAAO,CAACC,OAAOC,GAAG,CAAC,GAAGD,OAAOE,KAAK;YACpC;QACF;QACA,IAAIjB,SAASkB,aAAa,EAAE;YAC1BJ,OAAO,CAAC,mBAAmB,GAAG,MAAMvB,gBAAgBS,SAASkB,aAAa,EAAEjB;QAC9E;QACA,MAAMkB,aAAa,IAAIC;QACvB,MAAMC,QAAQ/B,WAAW,IAAM6B,WAAWG,KAAK,IAAIlB;QACnD,IAAI;YACF,MAAMmB,WAAW,MAAMC,MAAMC,OAAOzB,SAAS0B,GAAG,IAAI,KAAK;gBACvDjC,MAAMQ;gBACNa;gBACAa,QAAQ3B,SAAS2B,MAAM,IAAI;gBAC3BC,QAAQT,WAAWS,MAAM;YAC3B;YACAlB,aAAaa,SAASM,MAAM;YAC5B,IAAIN,SAASO,EAAE,EAAE;gBACf,OAAO;oBAAEA,IAAI;oBAAMD,QAAQN,SAASM,MAAM;oBAAElB;gBAAM;YACpD;YACAF,YAAY,CAAC,iBAAiB,EAAEc,SAASM,MAAM,CAAC,CAAC,CAAC;QACpD,EAAE,OAAOE,OAAO;YACdrB,aAAasB;YACbvB,YAAYU,WAAWS,MAAM,CAACK,OAAO,GACjC,CAAC,wBAAwB,EAAE7B,UAAU,IAAI,CAAC,GAC1C2B,iBAAiBG,QACfH,MAAMI,OAAO,GACbV,OAAOM;QACf,SAAU;YACRK,aAAaf;QACf;IACF;IACA,OAAO;QAAEU,OAAOtB;QAAWqB,IAAI;QAAOD,QAAQnB;QAAYC;IAAM;AAClE"}
@@ -0,0 +1,4 @@
1
+ import type { Endpoint } from 'payload';
2
+ import type { ResolvedOptions } from '../types.js';
3
+ /** `GET /api/<forms>/public/:slug` — the published form, ready to render, without delivery settings. */
4
+ export declare function publicFormEndpoint(options: ResolvedOptions): Endpoint;
@@ -0,0 +1,27 @@
1
+ import { getPublicForm } from '../server/forms.js';
2
+ /** `GET /api/<forms>/public/:slug` — the published form, ready to render, without delivery settings. */ export function publicFormEndpoint(options) {
3
+ return {
4
+ handler: async (req)=>{
5
+ const slug = String(req.routeParams?.slug || '');
6
+ const form = slug ? await getPublicForm(req.payload, slug, {
7
+ locale: req.locale || undefined,
8
+ req
9
+ }) : null;
10
+ if (!form) {
11
+ return Response.json({
12
+ message: options.words.notFound,
13
+ ok: false
14
+ }, {
15
+ status: 404
16
+ });
17
+ }
18
+ return Response.json(form, {
19
+ status: 200
20
+ });
21
+ },
22
+ method: 'get',
23
+ path: '/public/:slug'
24
+ };
25
+ }
26
+
27
+ //# sourceMappingURL=public.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/endpoints/public.ts"],"sourcesContent":["import type { Endpoint } from 'payload'\n\nimport type { ResolvedOptions } from '../types.js'\n\nimport { getPublicForm } from '../server/forms.js'\n\n/** `GET /api/<forms>/public/:slug` — the published form, ready to render, without delivery settings. */\nexport function publicFormEndpoint(options: ResolvedOptions): Endpoint {\n return {\n handler: async (req) => {\n const slug = String(req.routeParams?.slug || '')\n const form = slug ? await getPublicForm(req.payload, slug, { locale: (req.locale || undefined) as never, req }) : null\n if (!form) {\n return Response.json({ message: options.words.notFound, ok: false }, { status: 404 })\n }\n return Response.json(form, { status: 200 })\n },\n method: 'get',\n path: '/public/:slug',\n }\n}\n"],"names":["getPublicForm","publicFormEndpoint","options","handler","req","slug","String","routeParams","form","payload","locale","undefined","Response","json","message","words","notFound","ok","status","method","path"],"mappings":"AAIA,SAASA,aAAa,QAAQ,qBAAoB;AAElD,sGAAsG,GACtG,OAAO,SAASC,mBAAmBC,OAAwB;IACzD,OAAO;QACLC,SAAS,OAAOC;YACd,MAAMC,OAAOC,OAAOF,IAAIG,WAAW,EAAEF,QAAQ;YAC7C,MAAMG,OAAOH,OAAO,MAAML,cAAcI,IAAIK,OAAO,EAAEJ,MAAM;gBAAEK,QAASN,IAAIM,MAAM,IAAIC;gBAAqBP;YAAI,KAAK;YAClH,IAAI,CAACI,MAAM;gBACT,OAAOI,SAASC,IAAI,CAAC;oBAAEC,SAASZ,QAAQa,KAAK,CAACC,QAAQ;oBAAEC,IAAI;gBAAM,GAAG;oBAAEC,QAAQ;gBAAI;YACrF;YACA,OAAON,SAASC,IAAI,CAACL,MAAM;gBAAEU,QAAQ;YAAI;QAC3C;QACAC,QAAQ;QACRC,MAAM;IACR;AACF"}
@@ -0,0 +1,4 @@
1
+ import type { Endpoint } from 'payload';
2
+ import type { ResolvedOptions } from '../types.js';
3
+ /** `POST /api/<submissions>/:id/resend` — admins only: runs the failed outputs again. */
4
+ export declare function resendEndpoint(options: ResolvedOptions): Endpoint;
@@ -0,0 +1,58 @@
1
+ import { RESEND_CONTEXT } from '../hooks/resendTick.js';
2
+ import { resendFailedDeliveries } from '../server/resend.js';
3
+ /** `POST /api/<submissions>/:id/resend` — admins only: runs the failed outputs again. */ export function resendEndpoint(options) {
4
+ return {
5
+ handler: async (req)=>{
6
+ if (!req.user || !options.isAdmin(req)) {
7
+ return Response.json({
8
+ message: 'Forbidden',
9
+ ok: false
10
+ }, {
11
+ status: 403
12
+ });
13
+ }
14
+ const id = String(req.routeParams?.id || '');
15
+ let submission;
16
+ try {
17
+ submission = await req.payload.findByID({
18
+ id,
19
+ collection: options.slugs.submissions,
20
+ depth: 0,
21
+ overrideAccess: true
22
+ });
23
+ } catch {
24
+ return Response.json({
25
+ message: 'Not found',
26
+ ok: false
27
+ }, {
28
+ status: 404
29
+ });
30
+ }
31
+ const result = await resendFailedDeliveries(req.payload, submission, req);
32
+ if (result.added.length) {
33
+ await req.payload.update({
34
+ id,
35
+ collection: options.slugs.submissions,
36
+ context: {
37
+ [RESEND_CONTEXT]: true
38
+ },
39
+ data: {
40
+ deliveryLog: result.log,
41
+ status: result.status
42
+ },
43
+ overrideAccess: true
44
+ });
45
+ }
46
+ return Response.json({
47
+ deliveryLog: result.log,
48
+ ok: true,
49
+ resent: result.added.length,
50
+ status: result.status
51
+ });
52
+ },
53
+ method: 'post',
54
+ path: '/:id/resend'
55
+ };
56
+ }
57
+
58
+ //# sourceMappingURL=resend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/endpoints/resend.ts"],"sourcesContent":["import type { Endpoint } from 'payload'\n\nimport type { ResolvedOptions } from '../types.js'\n\nimport { RESEND_CONTEXT } from '../hooks/resendTick.js'\nimport { resendFailedDeliveries, type SubmissionDoc } from '../server/resend.js'\n\n/** `POST /api/<submissions>/:id/resend` — admins only: runs the failed outputs again. */\nexport function resendEndpoint(options: ResolvedOptions): Endpoint {\n return {\n handler: async (req) => {\n if (!req.user || !options.isAdmin(req)) {\n return Response.json({ message: 'Forbidden', ok: false }, { status: 403 })\n }\n const id = String(req.routeParams?.id || '')\n let submission: SubmissionDoc\n try {\n submission = (await req.payload.findByID({\n id,\n collection: options.slugs.submissions as never,\n depth: 0,\n overrideAccess: true,\n })) as unknown as SubmissionDoc\n } catch {\n return Response.json({ message: 'Not found', ok: false }, { status: 404 })\n }\n const result = await resendFailedDeliveries(req.payload, submission, req)\n if (result.added.length) {\n await req.payload.update({\n id,\n collection: options.slugs.submissions as never,\n context: { [RESEND_CONTEXT]: true },\n data: { deliveryLog: result.log, status: result.status } as never,\n overrideAccess: true,\n })\n }\n return Response.json({ deliveryLog: result.log, ok: true, resent: result.added.length, status: result.status })\n },\n method: 'post',\n path: '/:id/resend',\n }\n}\n"],"names":["RESEND_CONTEXT","resendFailedDeliveries","resendEndpoint","options","handler","req","user","isAdmin","Response","json","message","ok","status","id","String","routeParams","submission","payload","findByID","collection","slugs","submissions","depth","overrideAccess","result","added","length","update","context","data","deliveryLog","log","resent","method","path"],"mappings":"AAIA,SAASA,cAAc,QAAQ,yBAAwB;AACvD,SAASC,sBAAsB,QAA4B,sBAAqB;AAEhF,uFAAuF,GACvF,OAAO,SAASC,eAAeC,OAAwB;IACrD,OAAO;QACLC,SAAS,OAAOC;YACd,IAAI,CAACA,IAAIC,IAAI,IAAI,CAACH,QAAQI,OAAO,CAACF,MAAM;gBACtC,OAAOG,SAASC,IAAI,CAAC;oBAAEC,SAAS;oBAAaC,IAAI;gBAAM,GAAG;oBAAEC,QAAQ;gBAAI;YAC1E;YACA,MAAMC,KAAKC,OAAOT,IAAIU,WAAW,EAAEF,MAAM;YACzC,IAAIG;YACJ,IAAI;gBACFA,aAAc,MAAMX,IAAIY,OAAO,CAACC,QAAQ,CAAC;oBACvCL;oBACAM,YAAYhB,QAAQiB,KAAK,CAACC,WAAW;oBACrCC,OAAO;oBACPC,gBAAgB;gBAClB;YACF,EAAE,OAAM;gBACN,OAAOf,SAASC,IAAI,CAAC;oBAAEC,SAAS;oBAAaC,IAAI;gBAAM,GAAG;oBAAEC,QAAQ;gBAAI;YAC1E;YACA,MAAMY,SAAS,MAAMvB,uBAAuBI,IAAIY,OAAO,EAAED,YAAYX;YACrE,IAAImB,OAAOC,KAAK,CAACC,MAAM,EAAE;gBACvB,MAAMrB,IAAIY,OAAO,CAACU,MAAM,CAAC;oBACvBd;oBACAM,YAAYhB,QAAQiB,KAAK,CAACC,WAAW;oBACrCO,SAAS;wBAAE,CAAC5B,eAAe,EAAE;oBAAK;oBAClC6B,MAAM;wBAAEC,aAAaN,OAAOO,GAAG;wBAAEnB,QAAQY,OAAOZ,MAAM;oBAAC;oBACvDW,gBAAgB;gBAClB;YACF;YACA,OAAOf,SAASC,IAAI,CAAC;gBAAEqB,aAAaN,OAAOO,GAAG;gBAAEpB,IAAI;gBAAMqB,QAAQR,OAAOC,KAAK,CAACC,MAAM;gBAAEd,QAAQY,OAAOZ,MAAM;YAAC;QAC/G;QACAqB,QAAQ;QACRC,MAAM;IACR;AACF"}
@@ -0,0 +1,4 @@
1
+ import type { Endpoint } from 'payload';
2
+ import type { ResolvedOptions } from '../types.js';
3
+ /** `POST /api/<forms>/submit/:slug` — every check runs here, whatever the browser did. */
4
+ export declare function submitEndpoint(options: ResolvedOptions): Endpoint;
@@ -0,0 +1,320 @@
1
+ import { Buffer } from 'node:buffer';
2
+ import { hasDeliveries, runDeliveries, statusFromLog } from '../delivery/run.js';
3
+ import { checkRateLimit } from '../security/rateLimit.js';
4
+ import { getRequestIP, isHoneypotFilled } from '../security/request.js';
5
+ import { checkTurnstile } from '../security/turnstile.js';
6
+ import { loadPublishedForm } from '../server/forms.js';
7
+ import { buildSnapshot, checkAnswers } from '../shared/answers.js';
8
+ import { getFieldLabel, getInputFields } from '../shared/rules.js';
9
+ import { buildWhatsAppMessage, whatsappUrl } from '../shared/whatsapp.js';
10
+ function respond(status, body) {
11
+ return Response.json(body, {
12
+ status
13
+ });
14
+ }
15
+ function isFile(value) {
16
+ return typeof value === 'object' && value !== null && typeof value.arrayBuffer === 'function' && 'name' in value;
17
+ }
18
+ async function parseBody(req) {
19
+ const type = req.headers.get('content-type') || '';
20
+ const raw = {};
21
+ const files = {};
22
+ if (type.includes('multipart/form-data') || type.includes('application/x-www-form-urlencoded')) {
23
+ const data = await req.formData();
24
+ for (const key of new Set(data.keys())){
25
+ const values = data.getAll(key);
26
+ const fileValues = values.filter(isFile).filter((f)=>f.size > 0);
27
+ if (fileValues.length) {
28
+ files[key] = fileValues;
29
+ continue;
30
+ }
31
+ const text = values.filter((v)=>typeof v === 'string');
32
+ raw[key] = text.length > 1 ? text : text[0];
33
+ }
34
+ return {
35
+ files,
36
+ raw
37
+ };
38
+ }
39
+ const json = await req.json();
40
+ if (json && typeof json === 'object' && !Array.isArray(json)) {
41
+ Object.assign(raw, json);
42
+ }
43
+ return {
44
+ files,
45
+ raw
46
+ };
47
+ }
48
+ /** `POST /api/<forms>/submit/:slug` — every check runs here, whatever the browser did. */ export function submitEndpoint(options) {
49
+ return {
50
+ handler: async (req)=>{
51
+ const payload = req.payload;
52
+ const slug = String(req.routeParams?.slug || '');
53
+ const locale = req.locale || undefined;
54
+ const loaded = slug ? await loadPublishedForm(payload, slug, {
55
+ locale,
56
+ req
57
+ }) : null;
58
+ if (!loaded) {
59
+ return respond(404, {
60
+ message: options.words.notFound,
61
+ ok: false
62
+ });
63
+ }
64
+ const { delivery, form } = loaded;
65
+ const words = form.wording;
66
+ if (form.intakeState === 'closed') {
67
+ return respond(409, {
68
+ message: words.closed,
69
+ ok: false
70
+ });
71
+ }
72
+ let body;
73
+ try {
74
+ body = await parseBody(req);
75
+ } catch {
76
+ return respond(400, {
77
+ message: words.genericError,
78
+ ok: false
79
+ });
80
+ }
81
+ const { files, raw } = body;
82
+ const ip = getRequestIP(req.headers);
83
+ if (form.spamChecks) {
84
+ if (isHoneypotFilled(raw, options.honeypotField)) {
85
+ return respond(400, {
86
+ message: words.genericError,
87
+ ok: false
88
+ });
89
+ }
90
+ const human = await checkTurnstile({
91
+ ip,
92
+ logger: payload.logger,
93
+ settings: options.turnstile,
94
+ token: raw[options.turnstile.fieldName]
95
+ });
96
+ if (!human) {
97
+ return respond(400, {
98
+ message: words.securityCheckFailed,
99
+ ok: false
100
+ });
101
+ }
102
+ if (options.rateLimit) {
103
+ const allowed = await checkRateLimit({
104
+ formId: form.id,
105
+ ip,
106
+ max: options.rateLimit.max,
107
+ payload,
108
+ windowSeconds: options.rateLimit.windowSeconds
109
+ });
110
+ if (!allowed) {
111
+ return respond(429, {
112
+ message: words.tooManyTries,
113
+ ok: false
114
+ });
115
+ }
116
+ }
117
+ }
118
+ const fileLikes = {};
119
+ for (const [key, list] of Object.entries(files)){
120
+ fileLikes[key] = list.map((f)=>({
121
+ name: f.name,
122
+ type: f.type,
123
+ size: f.size
124
+ }));
125
+ }
126
+ const checked = checkAnswers(form, raw, {
127
+ fieldTypes: options.fieldTypes,
128
+ files: fileLikes,
129
+ limits: options.attachments || undefined,
130
+ words
131
+ });
132
+ if (!checked.ok) {
133
+ return respond(400, {
134
+ fieldErrors: checked.fieldErrors,
135
+ message: words.errorPrefix,
136
+ ok: false
137
+ });
138
+ }
139
+ const answers = checked.answers;
140
+ let waUrl;
141
+ if (delivery.whatsapp.enabled) {
142
+ const number = form.whatsapp?.number;
143
+ if (!number) {
144
+ payload.logger.error(`payload-dynamic-forms: form "${form.slug}" has WhatsApp on but no number (form or whatsapp.number).`);
145
+ return respond(500, {
146
+ message: words.genericError,
147
+ ok: false
148
+ });
149
+ }
150
+ const message = buildWhatsAppMessage(form, answers, {
151
+ fieldTypes: options.fieldTypes,
152
+ rowTemplate: delivery.whatsapp.rowTemplate,
153
+ template: delivery.whatsapp.messageTemplate
154
+ });
155
+ waUrl = whatsappUrl(number, message);
156
+ }
157
+ const inputs = new Map(getInputFields(form).map((entry)=>[
158
+ entry.key,
159
+ entry
160
+ ]));
161
+ const visibleUploads = checked.fileKeys.map((key)=>inputs.get(key)).filter(Boolean);
162
+ let submissionId;
163
+ let snapshot = buildSnapshot(form, answers, options.fieldTypes);
164
+ const created = [];
165
+ const outputs = hasDeliveries(delivery);
166
+ if (delivery.store) {
167
+ const attachmentSlug = options.attachments?.collection;
168
+ try {
169
+ for (const { field, key } of visibleUploads){
170
+ for (const file of files[key]){
171
+ const doc = await payload.create({
172
+ collection: attachmentSlug,
173
+ data: {
174
+ fieldKey: key,
175
+ fieldLabel: getFieldLabel(field, key),
176
+ form: form.id
177
+ },
178
+ file: {
179
+ name: file.name || 'upload',
180
+ data: Buffer.from(await file.arrayBuffer()),
181
+ mimetype: file.type || 'application/octet-stream',
182
+ size: file.size
183
+ },
184
+ overrideAccess: true
185
+ });
186
+ const filename = String(doc.filename || file.name);
187
+ created.push({
188
+ id: doc.id,
189
+ fieldKey: key,
190
+ filename
191
+ });
192
+ const prev = answers[key];
193
+ answers[key] = {
194
+ attachmentIds: [
195
+ ...prev?.attachmentIds || [],
196
+ doc.id
197
+ ],
198
+ filenames: [
199
+ ...prev?.filenames || [],
200
+ filename
201
+ ]
202
+ };
203
+ }
204
+ }
205
+ snapshot = buildSnapshot(form, answers, options.fieldTypes);
206
+ const submission = await payload.create({
207
+ collection: options.slugs.submissions,
208
+ data: {
209
+ answers,
210
+ attachments: created.map((c)=>c.id),
211
+ deliveryLog: [],
212
+ form: form.id,
213
+ metadata: {
214
+ ip: options.metadata.ip ? ip || null : null,
215
+ locale: req.locale || null,
216
+ userAgent: options.metadata.userAgent ? req.headers.get('user-agent') || null : null
217
+ },
218
+ snapshot,
219
+ status: outputs ? 'received' : 'processed'
220
+ },
221
+ overrideAccess: true
222
+ });
223
+ submissionId = submission.id;
224
+ for (const file of created){
225
+ await payload.update({
226
+ id: file.id,
227
+ collection: attachmentSlug,
228
+ data: {
229
+ submission: submissionId
230
+ },
231
+ overrideAccess: true
232
+ });
233
+ }
234
+ } catch (error) {
235
+ payload.logger.error({
236
+ err: error,
237
+ msg: `payload-dynamic-forms: could not store a submission of "${form.slug}"`
238
+ });
239
+ for (const file of created){
240
+ try {
241
+ await payload.delete({
242
+ id: file.id,
243
+ collection: attachmentSlug,
244
+ overrideAccess: true
245
+ });
246
+ } catch (deleteError) {
247
+ payload.logger.error({
248
+ err: deleteError,
249
+ msg: 'payload-dynamic-forms: could not remove an attachment'
250
+ });
251
+ }
252
+ }
253
+ return respond(500, {
254
+ message: words.genericError,
255
+ ok: false
256
+ });
257
+ }
258
+ }
259
+ if (outputs) {
260
+ const task = async ()=>{
261
+ try {
262
+ const entries = await runDeliveries({
263
+ answers,
264
+ attachments: created,
265
+ delivery,
266
+ form,
267
+ options,
268
+ payload,
269
+ req,
270
+ snapshot,
271
+ submissionId
272
+ });
273
+ for (const entry of entries.filter((e)=>e.result === 'failed')){
274
+ payload.logger.error(`payload-dynamic-forms: ${entry.kind} "${entry.label}" of "${form.slug}" failed: ${entry.error}`);
275
+ }
276
+ if (submissionId !== undefined) {
277
+ await payload.update({
278
+ id: submissionId,
279
+ collection: options.slugs.submissions,
280
+ data: {
281
+ deliveryLog: entries,
282
+ status: statusFromLog(entries)
283
+ },
284
+ overrideAccess: true
285
+ });
286
+ }
287
+ } catch (error) {
288
+ payload.logger.error({
289
+ err: error,
290
+ msg: `payload-dynamic-forms: delivery of "${form.slug}" failed`
291
+ });
292
+ }
293
+ };
294
+ if (options.delivery?.schedule) {
295
+ options.delivery.schedule(task);
296
+ } else {
297
+ await task();
298
+ }
299
+ }
300
+ const result = {
301
+ message: words.success,
302
+ ok: true
303
+ };
304
+ if (form.successType === 'redirect' && form.successRedirectURL) {
305
+ result.redirectURL = form.successRedirectURL;
306
+ }
307
+ if (submissionId !== undefined) {
308
+ result.submissionId = submissionId;
309
+ }
310
+ if (waUrl) {
311
+ result.whatsappUrl = waUrl;
312
+ }
313
+ return respond(200, result);
314
+ },
315
+ method: 'post',
316
+ path: '/submit/:slug'
317
+ };
318
+ }
319
+
320
+ //# sourceMappingURL=submit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/endpoints/submit.ts"],"sourcesContent":["import type { Endpoint, PayloadRequest } from 'payload'\n\nimport { Buffer } from 'node:buffer'\n\nimport type { Answers, FileAnswer, FileLike, SubmitResult } from '../shared/types.js'\nimport type { ResolvedOptions } from '../types.js'\n\nimport { hasDeliveries, type LogEntry, runDeliveries, statusFromLog } from '../delivery/run.js'\nimport { checkRateLimit } from '../security/rateLimit.js'\nimport { getRequestIP, isHoneypotFilled } from '../security/request.js'\nimport { checkTurnstile } from '../security/turnstile.js'\nimport { loadPublishedForm } from '../server/forms.js'\nimport { buildSnapshot, checkAnswers } from '../shared/answers.js'\nimport { getFieldLabel, getInputFields } from '../shared/rules.js'\nimport { buildWhatsAppMessage, whatsappUrl } from '../shared/whatsapp.js'\n\ntype Raw = Record<string, unknown>\n\nfunction respond(status: number, body: SubmitResult): Response {\n return Response.json(body, { status })\n}\n\nfunction isFile(value: unknown): value is File {\n return typeof value === 'object' && value !== null && typeof (value as Blob).arrayBuffer === 'function' && 'name' in value\n}\n\nasync function parseBody(req: PayloadRequest): Promise<{ files: Record<string, File[]>; raw: Raw }> {\n const type = req.headers.get('content-type') || ''\n const raw: Raw = {}\n const files: Record<string, File[]> = {}\n if (type.includes('multipart/form-data') || type.includes('application/x-www-form-urlencoded')) {\n const data = await req.formData!()\n for (const key of new Set(data.keys())) {\n const values = data.getAll(key)\n const fileValues = values.filter(isFile).filter((f) => f.size > 0)\n if (fileValues.length) {\n files[key] = fileValues\n continue\n }\n const text = values.filter((v): v is string => typeof v === 'string')\n raw[key] = text.length > 1 ? text : text[0]\n }\n return { files, raw }\n }\n const json = (await req.json!()) as unknown\n if (json && typeof json === 'object' && !Array.isArray(json)) {\n Object.assign(raw, json)\n }\n return { files, raw }\n}\n\n/** `POST /api/<forms>/submit/:slug` — every check runs here, whatever the browser did. */\nexport function submitEndpoint(options: ResolvedOptions): Endpoint {\n return {\n handler: async (req) => {\n const payload = req.payload\n const slug = String(req.routeParams?.slug || '')\n const locale = (req.locale || undefined) as never\n const loaded = slug ? await loadPublishedForm(payload, slug, { locale, req }) : null\n if (!loaded) {\n return respond(404, { message: options.words.notFound, ok: false })\n }\n const { delivery, form } = loaded\n const words = form.wording\n if (form.intakeState === 'closed') {\n return respond(409, { message: words.closed, ok: false })\n }\n\n let body: Awaited<ReturnType<typeof parseBody>>\n try {\n body = await parseBody(req)\n } catch {\n return respond(400, { message: words.genericError, ok: false })\n }\n const { files, raw } = body\n const ip = getRequestIP(req.headers)\n\n if (form.spamChecks) {\n if (isHoneypotFilled(raw, options.honeypotField)) {\n return respond(400, { message: words.genericError, ok: false })\n }\n const human = await checkTurnstile({\n ip,\n logger: payload.logger,\n settings: options.turnstile,\n token: raw[options.turnstile.fieldName],\n })\n if (!human) {\n return respond(400, { message: words.securityCheckFailed, ok: false })\n }\n if (options.rateLimit) {\n const allowed = await checkRateLimit({\n formId: form.id,\n ip,\n max: options.rateLimit.max,\n payload,\n windowSeconds: options.rateLimit.windowSeconds,\n })\n if (!allowed) {\n return respond(429, { message: words.tooManyTries, ok: false })\n }\n }\n }\n\n const fileLikes: Record<string, FileLike[]> = {}\n for (const [key, list] of Object.entries(files)) {\n fileLikes[key] = list.map((f) => ({ name: f.name, type: f.type, size: f.size }))\n }\n const checked = checkAnswers(form, raw, {\n fieldTypes: options.fieldTypes,\n files: fileLikes,\n limits: options.attachments || undefined,\n words,\n })\n if (!checked.ok) {\n return respond(400, { fieldErrors: checked.fieldErrors, message: words.errorPrefix, ok: false })\n }\n const answers: Answers = checked.answers\n\n let waUrl: string | undefined\n if (delivery.whatsapp.enabled) {\n const number = form.whatsapp?.number\n if (!number) {\n payload.logger.error(`payload-dynamic-forms: form \"${form.slug}\" has WhatsApp on but no number (form or whatsapp.number).`)\n return respond(500, { message: words.genericError, ok: false })\n }\n const message = buildWhatsAppMessage(form, answers, {\n fieldTypes: options.fieldTypes,\n rowTemplate: delivery.whatsapp.rowTemplate,\n template: delivery.whatsapp.messageTemplate,\n })\n waUrl = whatsappUrl(number, message)\n }\n\n const inputs = new Map(getInputFields(form).map((entry) => [entry.key, entry]))\n const visibleUploads = checked.fileKeys.map((key) => inputs.get(key)!).filter(Boolean)\n\n let submissionId: number | string | undefined\n let snapshot = buildSnapshot(form, answers, options.fieldTypes)\n const created: Array<{ fieldKey: string; filename: string; id: number | string }> = []\n const outputs = hasDeliveries(delivery)\n\n if (delivery.store) {\n const attachmentSlug = options.attachments?.collection\n try {\n for (const { field, key } of visibleUploads) {\n for (const file of files[key]) {\n const doc = await payload.create({\n collection: attachmentSlug as never,\n data: { fieldKey: key, fieldLabel: getFieldLabel(field, key), form: form.id } as never,\n file: {\n name: file.name || 'upload',\n data: Buffer.from(await file.arrayBuffer()),\n mimetype: file.type || 'application/octet-stream',\n size: file.size,\n },\n overrideAccess: true,\n })\n const filename = String((doc as { filename?: string }).filename || file.name)\n created.push({ id: doc.id, fieldKey: key, filename })\n const prev = answers[key] as FileAnswer | undefined\n answers[key] = {\n attachmentIds: [...(prev?.attachmentIds || []), doc.id],\n filenames: [...(prev?.filenames || []), filename],\n }\n }\n }\n snapshot = buildSnapshot(form, answers, options.fieldTypes)\n const submission = await payload.create({\n collection: options.slugs.submissions as never,\n data: {\n answers,\n attachments: created.map((c) => c.id),\n deliveryLog: [],\n form: form.id,\n metadata: {\n ip: options.metadata.ip ? ip || null : null,\n locale: req.locale || null,\n userAgent: options.metadata.userAgent ? req.headers.get('user-agent') || null : null,\n },\n snapshot,\n status: outputs ? 'received' : 'processed',\n } as never,\n overrideAccess: true,\n })\n submissionId = submission.id\n for (const file of created) {\n await payload.update({\n id: file.id,\n collection: attachmentSlug as never,\n data: { submission: submissionId } as never,\n overrideAccess: true,\n })\n }\n } catch (error) {\n payload.logger.error({ err: error, msg: `payload-dynamic-forms: could not store a submission of \"${form.slug}\"` })\n for (const file of created) {\n try {\n await payload.delete({ id: file.id, collection: attachmentSlug as never, overrideAccess: true })\n } catch (deleteError) {\n payload.logger.error({ err: deleteError, msg: 'payload-dynamic-forms: could not remove an attachment' })\n }\n }\n return respond(500, { message: words.genericError, ok: false })\n }\n }\n\n if (outputs) {\n const task = async () => {\n try {\n const entries: LogEntry[] = await runDeliveries({\n answers,\n attachments: created,\n delivery,\n form,\n options,\n payload,\n req,\n snapshot,\n submissionId,\n })\n for (const entry of entries.filter((e) => e.result === 'failed')) {\n payload.logger.error(`payload-dynamic-forms: ${entry.kind} \"${entry.label}\" of \"${form.slug}\" failed: ${entry.error}`)\n }\n if (submissionId !== undefined) {\n await payload.update({\n id: submissionId,\n collection: options.slugs.submissions as never,\n data: { deliveryLog: entries, status: statusFromLog(entries) } as never,\n overrideAccess: true,\n })\n }\n } catch (error) {\n payload.logger.error({ err: error, msg: `payload-dynamic-forms: delivery of \"${form.slug}\" failed` })\n }\n }\n if (options.delivery?.schedule) {\n options.delivery.schedule(task)\n } else {\n await task()\n }\n }\n\n const result: SubmitResult = { message: words.success, ok: true }\n if (form.successType === 'redirect' && form.successRedirectURL) {\n result.redirectURL = form.successRedirectURL\n }\n if (submissionId !== undefined) {\n result.submissionId = submissionId\n }\n if (waUrl) {\n result.whatsappUrl = waUrl\n }\n return respond(200, result)\n },\n method: 'post',\n path: '/submit/:slug',\n }\n}\n"],"names":["Buffer","hasDeliveries","runDeliveries","statusFromLog","checkRateLimit","getRequestIP","isHoneypotFilled","checkTurnstile","loadPublishedForm","buildSnapshot","checkAnswers","getFieldLabel","getInputFields","buildWhatsAppMessage","whatsappUrl","respond","status","body","Response","json","isFile","value","arrayBuffer","parseBody","req","type","headers","get","raw","files","includes","data","formData","key","Set","keys","values","getAll","fileValues","filter","f","size","length","text","v","Array","isArray","Object","assign","submitEndpoint","options","handler","payload","slug","String","routeParams","locale","undefined","loaded","message","words","notFound","ok","delivery","form","wording","intakeState","closed","genericError","ip","spamChecks","honeypotField","human","logger","settings","turnstile","token","fieldName","securityCheckFailed","rateLimit","allowed","formId","id","max","windowSeconds","tooManyTries","fileLikes","list","entries","map","name","checked","fieldTypes","limits","attachments","fieldErrors","errorPrefix","answers","waUrl","whatsapp","enabled","number","error","rowTemplate","template","messageTemplate","inputs","Map","entry","visibleUploads","fileKeys","Boolean","submissionId","snapshot","created","outputs","store","attachmentSlug","collection","field","file","doc","create","fieldKey","fieldLabel","from","mimetype","overrideAccess","filename","push","prev","attachmentIds","filenames","submission","slugs","submissions","c","deliveryLog","metadata","userAgent","update","err","msg","delete","deleteError","task","e","result","kind","label","schedule","success","successType","successRedirectURL","redirectURL","method","path"],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * `payload-dynamic-forms/shared`: the rule engine, answer checks, tokens,
3
+ * messages and words. Runs the same in the browser, on Workers and in the submit endpoint.
4
+ * Imports nothing from Payload, React or Node at runtime.
5
+ */
6
+ export { answerToText, buildSnapshot, checkAnswers, checkFiles, type CheckOptions, type CheckResult, DEFAULT_FILE_LIMITS, type FileLimits, isValidEmail, normalizeAnswer, } from '../shared/answers.js';
7
+ export { MATCH_MODES, OPERATOR_VALUES, OPERATORS, type OperatorValue } from '../shared/operators.js';
8
+ export { defaultAnswer, evaluateRow, evaluateRules, getFieldKey, getFieldLabel, getInputFieldMap, getInputFields, getOptions, getRowLabel, getSectionKey, getSections, getVisibleSections, hasAnswer, initialAnswers, type InputEntry, isChoiceBlock, isFieldVisible, isInputBlock, isSectionVisible, normalizeKey, readRuleValue, splitRuleKey, } from '../shared/rules.js';
9
+ export { getNextSectionKey, getStepPath, getStepProgress, getVisibility, type Visibility } from '../shared/steps.js';
10
+ export { buildMessageRows, buildTemplateValues, escapeHtml, interpolate, type InterpolateOptions, type MessageRow, type TokenValue, } from '../shared/tokens.js';
11
+ export type * from '../shared/types.js';
12
+ export { buildWhatsAppMessage, DEFAULT_WHATSAPP_ROW_TEMPLATE, DEFAULT_WHATSAPP_TEMPLATE, type WhatsAppMessageOptions, whatsappUrl, } from '../shared/whatsapp.js';
13
+ export { DEFAULT_WORDS, fillWords, WORD_KEYS } from '../shared/words.js';
@@ -0,0 +1,13 @@
1
+ /**
2
+ * `payload-dynamic-forms/shared`: the rule engine, answer checks, tokens,
3
+ * messages and words. Runs the same in the browser, on Workers and in the submit endpoint.
4
+ * Imports nothing from Payload, React or Node at runtime.
5
+ */ export { answerToText, buildSnapshot, checkAnswers, checkFiles, DEFAULT_FILE_LIMITS, isValidEmail, normalizeAnswer } from '../shared/answers.js';
6
+ export { MATCH_MODES, OPERATOR_VALUES, OPERATORS } from '../shared/operators.js';
7
+ export { defaultAnswer, evaluateRow, evaluateRules, getFieldKey, getFieldLabel, getInputFieldMap, getInputFields, getOptions, getRowLabel, getSectionKey, getSections, getVisibleSections, hasAnswer, initialAnswers, isChoiceBlock, isFieldVisible, isInputBlock, isSectionVisible, normalizeKey, readRuleValue, splitRuleKey } from '../shared/rules.js';
8
+ export { getNextSectionKey, getStepPath, getStepProgress, getVisibility } from '../shared/steps.js';
9
+ export { buildMessageRows, buildTemplateValues, escapeHtml, interpolate } from '../shared/tokens.js';
10
+ export { buildWhatsAppMessage, DEFAULT_WHATSAPP_ROW_TEMPLATE, DEFAULT_WHATSAPP_TEMPLATE, whatsappUrl } from '../shared/whatsapp.js';
11
+ export { DEFAULT_WORDS, fillWords, WORD_KEYS } from '../shared/words.js';
12
+
13
+ //# sourceMappingURL=shared.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/exports/shared.ts"],"sourcesContent":["/**\n * `payload-dynamic-forms/shared`: the rule engine, answer checks, tokens,\n * messages and words. Runs the same in the browser, on Workers and in the submit endpoint.\n * Imports nothing from Payload, React or Node at runtime.\n */\nexport {\n answerToText,\n buildSnapshot,\n checkAnswers,\n checkFiles,\n type CheckOptions,\n type CheckResult,\n DEFAULT_FILE_LIMITS,\n type FileLimits,\n isValidEmail,\n normalizeAnswer,\n} from '../shared/answers.js'\nexport { MATCH_MODES, OPERATOR_VALUES, OPERATORS, type OperatorValue } from '../shared/operators.js'\nexport {\n defaultAnswer,\n evaluateRow,\n evaluateRules,\n getFieldKey,\n getFieldLabel,\n getInputFieldMap,\n getInputFields,\n getOptions,\n getRowLabel,\n getSectionKey,\n getSections,\n getVisibleSections,\n hasAnswer,\n initialAnswers,\n type InputEntry,\n isChoiceBlock,\n isFieldVisible,\n isInputBlock,\n isSectionVisible,\n normalizeKey,\n readRuleValue,\n splitRuleKey,\n} from '../shared/rules.js'\nexport { getNextSectionKey, getStepPath, getStepProgress, getVisibility, type Visibility } from '../shared/steps.js'\nexport {\n buildMessageRows,\n buildTemplateValues,\n escapeHtml,\n interpolate,\n type InterpolateOptions,\n type MessageRow,\n type TokenValue,\n} from '../shared/tokens.js'\nexport type * from '../shared/types.js'\nexport {\n buildWhatsAppMessage,\n DEFAULT_WHATSAPP_ROW_TEMPLATE,\n DEFAULT_WHATSAPP_TEMPLATE,\n type WhatsAppMessageOptions,\n whatsappUrl,\n} from '../shared/whatsapp.js'\nexport { DEFAULT_WORDS, fillWords, WORD_KEYS } from '../shared/words.js'\n"],"names":["answerToText","buildSnapshot","checkAnswers","checkFiles","DEFAULT_FILE_LIMITS","isValidEmail","normalizeAnswer","MATCH_MODES","OPERATOR_VALUES","OPERATORS","defaultAnswer","evaluateRow","evaluateRules","getFieldKey","getFieldLabel","getInputFieldMap","getInputFields","getOptions","getRowLabel","getSectionKey","getSections","getVisibleSections","hasAnswer","initialAnswers","isChoiceBlock","isFieldVisible","isInputBlock","isSectionVisible","normalizeKey","readRuleValue","splitRuleKey","getNextSectionKey","getStepPath","getStepProgress","getVisibility","buildMessageRows","buildTemplateValues","escapeHtml","interpolate","buildWhatsAppMessage","DEFAULT_WHATSAPP_ROW_TEMPLATE","DEFAULT_WHATSAPP_TEMPLATE","whatsappUrl","DEFAULT_WORDS","fillWords","WORD_KEYS"],"mappings":"AAAA;;;;CAIC,GACD,SACEA,YAAY,EACZC,aAAa,EACbC,YAAY,EACZC,UAAU,EAGVC,mBAAmB,EAEnBC,YAAY,EACZC,eAAe,QACV,uBAAsB;AAC7B,SAASC,WAAW,EAAEC,eAAe,EAAEC,SAAS,QAA4B,yBAAwB;AACpG,SACEC,aAAa,EACbC,WAAW,EACXC,aAAa,EACbC,WAAW,EACXC,aAAa,EACbC,gBAAgB,EAChBC,cAAc,EACdC,UAAU,EACVC,WAAW,EACXC,aAAa,EACbC,WAAW,EACXC,kBAAkB,EAClBC,SAAS,EACTC,cAAc,EAEdC,aAAa,EACbC,cAAc,EACdC,YAAY,EACZC,gBAAgB,EAChBC,YAAY,EACZC,aAAa,EACbC,YAAY,QACP,qBAAoB;AAC3B,SAASC,iBAAiB,EAAEC,WAAW,EAAEC,eAAe,EAAEC,aAAa,QAAyB,qBAAoB;AACpH,SACEC,gBAAgB,EAChBC,mBAAmB,EACnBC,UAAU,EACVC,WAAW,QAIN,sBAAqB;AAE5B,SACEC,oBAAoB,EACpBC,6BAA6B,EAC7BC,yBAAyB,EAEzBC,WAAW,QACN,wBAAuB;AAC9B,SAASC,aAAa,EAAEC,SAAS,EAAEC,SAAS,QAAQ,qBAAoB"}
@@ -0,0 +1,13 @@
1
+ import type { Block } from 'payload';
2
+ export type FormBlockOptions = {
3
+ /** Forms collection slug (default `custom-forms`; use the plugin's `slugs.forms` if renamed). */
4
+ formsSlug?: string;
5
+ interfaceName?: string;
6
+ /** Block slug, default `formBlock`. */
7
+ slug?: string;
8
+ };
9
+ /**
10
+ * A page block that picks one form. Add it to any blocks field; the app's renderer reads the
11
+ * form with `getPublicForm` or the public endpoint. It holds no words of its own.
12
+ */
13
+ export declare function formBlock(options?: FormBlockOptions): Block;