payload-dynamic-forms 0.0.0-stage → 0.2.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 +461 -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 +558 -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 +37 -0
  25. package/dist/delivery/run.js +234 -0
  26. package/dist/delivery/run.js.map +1 -0
  27. package/dist/delivery/webhook.d.ts +41 -0
  28. package/dist/delivery/webhook.js +120 -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 +130 -0
  53. package/dist/index.js.map +1 -0
  54. package/dist/security/crypto.d.ts +9 -0
  55. package/dist/security/crypto.js +54 -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 +182 -0
  97. package/dist/types.js +3 -0
  98. package/dist/types.js.map +1 -0
  99. package/package.json +114 -4
@@ -0,0 +1,215 @@
1
+ import { createLocalReq } from 'payload';
2
+ import { isChoiceBlock, isInputBlock } from '../shared/rules.js';
3
+ import { fillWords } from '../shared/words.js';
4
+ /** The plugin options of an app, kept on its config by `formsPlugin`. */ export function getPluginOptions(payload) {
5
+ const options = payload.config.custom?.payloadForms;
6
+ if (!options) {
7
+ throw new Error('payload-dynamic-forms: formsPlugin is not installed (or is switched off) in this config.');
8
+ }
9
+ return options;
10
+ }
11
+ function pick(doc, names = []) {
12
+ const out = {};
13
+ for (const name of names){
14
+ if (doc[name] !== undefined && doc[name] !== null) {
15
+ out[name] = doc[name];
16
+ }
17
+ }
18
+ return out;
19
+ }
20
+ /**
21
+ * Turns source-based choices into `{ label, value, attributes }`: picked items in their order, or
22
+ * every published item in the source's default sort when the pick is empty. Unpublished and
23
+ * missing items are left out.
24
+ */ async function resolveChoices(payload, options, sections, locale) {
25
+ const sources = options.optionSources || {};
26
+ for (const section of sections){
27
+ for (const field of section.fields || []){
28
+ if (!isInputBlock(field) || !isChoiceBlock(field)) {
29
+ continue;
30
+ }
31
+ const block = field;
32
+ const sourceKey = block.optionsFrom;
33
+ const source = sourceKey && sourceKey !== 'typed' ? sources[sourceKey] : undefined;
34
+ const picked = Array.isArray(block.sourceItems) ? block.sourceItems : [];
35
+ delete block.sourceItems;
36
+ if (!source) {
37
+ block.options = (block.options || []).map((o)=>({
38
+ label: o.label,
39
+ value: o.value
40
+ }));
41
+ continue;
42
+ }
43
+ const slug = String(source.collection);
44
+ const config = payload.collections[slug]?.config;
45
+ if (!config) {
46
+ block.options = [];
47
+ continue;
48
+ }
49
+ const ids = picked.filter((item)=>item && item.relationTo === slug).map((item)=>item.value && typeof item.value === 'object' ? item.value.id : item.value);
50
+ const and = [];
51
+ if (picked.length) {
52
+ and.push({
53
+ id: {
54
+ in: ids.length ? ids : [
55
+ -1
56
+ ]
57
+ }
58
+ });
59
+ }
60
+ if (config.versions?.drafts) {
61
+ and.push({
62
+ _status: {
63
+ equals: 'published'
64
+ }
65
+ });
66
+ }
67
+ // Trusted read of the items the editor picked: only label, value and listed attributes leave it.
68
+ const { docs } = await payload.find({
69
+ collection: slug,
70
+ depth: 0,
71
+ draft: false,
72
+ locale: locale,
73
+ overrideAccess: true,
74
+ pagination: false,
75
+ where: and.length ? {
76
+ and
77
+ } : undefined
78
+ });
79
+ const byId = new Map(docs.map((doc)=>[
80
+ String(doc.id),
81
+ doc
82
+ ]));
83
+ const ordered = picked.length ? ids.map((id)=>byId.get(String(id))).filter((d)=>Boolean(d)) : docs;
84
+ block.options = ordered.map((doc)=>({
85
+ attributes: pick(doc, source.attributes),
86
+ label: String(doc[source.labelField] ?? ''),
87
+ value: String(doc[source.valueField] ?? '')
88
+ })).filter((o)=>o.value);
89
+ }
90
+ }
91
+ }
92
+ async function buildLoaded(payload, options, raw, locale, req) {
93
+ const sections = structuredClone(raw.sections || []).filter(Boolean);
94
+ await resolveChoices(payload, options, sections, locale);
95
+ const d = raw.delivery || {};
96
+ const delivery = {
97
+ emails: (d.emails || []).filter(Boolean),
98
+ store: d.store !== false,
99
+ webhooks: (d.webhooks || []).filter(Boolean),
100
+ whatsapp: {
101
+ enabled: false,
102
+ ...d.whatsapp || {}
103
+ }
104
+ };
105
+ const sends = delivery.emails.some((e)=>e.enabled !== false) || delivery.webhooks.some((w)=>w.enabled !== false);
106
+ const form = {
107
+ id: raw.id,
108
+ slug: String(raw.slug || ''),
109
+ honeypotField: options.honeypotField,
110
+ intakeState: raw.intakeState === 'closed' ? 'closed' : 'open',
111
+ presentationMode: raw.presentationMode === 'steps' ? 'steps' : 'singlePage',
112
+ sections,
113
+ spamChecks: delivery.store || sends,
114
+ successRedirectURL: raw.successType === 'redirect' ? raw.successRedirectURL || null : null,
115
+ successType: raw.successType === 'redirect' ? 'redirect' : 'message',
116
+ title: String(raw.title || ''),
117
+ turnstile: {
118
+ enabled: options.turnstile.enabled,
119
+ fieldName: options.turnstile.fieldName
120
+ },
121
+ wording: fillWords(raw.wording, options.words)
122
+ };
123
+ if (delivery.whatsapp.enabled) {
124
+ let number = delivery.whatsapp.number?.trim() || '';
125
+ if (!number && options.whatsapp?.number) {
126
+ const request = req || await createLocalReq({}, payload);
127
+ number = String(await options.whatsapp.number({
128
+ form,
129
+ req: request
130
+ }) || '').trim();
131
+ }
132
+ if (number) {
133
+ form.whatsapp = {
134
+ number
135
+ };
136
+ }
137
+ }
138
+ return {
139
+ delivery,
140
+ form
141
+ };
142
+ }
143
+ /**
144
+ * Loads a published form by slug, as a visitor would see it, plus its delivery settings for the
145
+ * server. Null when the slug is unknown or the form is not published.
146
+ */ export async function loadPublishedForm(payload, slug, args = {}) {
147
+ const options = getPluginOptions(payload);
148
+ // Read as an anonymous visitor: access control decides, delivery fields are stripped.
149
+ const { docs } = await payload.find({
150
+ collection: options.slugs.forms,
151
+ depth: 0,
152
+ draft: false,
153
+ limit: 1,
154
+ locale: args.locale,
155
+ overrideAccess: false,
156
+ pagination: false,
157
+ where: {
158
+ and: [
159
+ {
160
+ slug: {
161
+ equals: slug
162
+ }
163
+ },
164
+ {
165
+ _status: {
166
+ equals: 'published'
167
+ }
168
+ }
169
+ ]
170
+ }
171
+ });
172
+ const visible = docs[0];
173
+ if (!visible) {
174
+ return null;
175
+ }
176
+ // Server-side read of the same published document for its delivery settings and secrets.
177
+ const full = await payload.findByID({
178
+ id: visible.id,
179
+ collection: options.slugs.forms,
180
+ depth: 0,
181
+ draft: false,
182
+ locale: args.locale,
183
+ overrideAccess: true
184
+ });
185
+ return buildLoaded(payload, options, {
186
+ ...full,
187
+ id: visible.id
188
+ }, args.locale, args.req);
189
+ }
190
+ /** Loads a form by id with its delivery settings (trusted server use: resend). */ export async function loadFormById(payload, id, args = {}) {
191
+ const options = getPluginOptions(payload);
192
+ try {
193
+ const full = await payload.findByID({
194
+ id,
195
+ collection: options.slugs.forms,
196
+ depth: 0,
197
+ draft: false,
198
+ locale: args.locale,
199
+ overrideAccess: true
200
+ });
201
+ return full ? buildLoaded(payload, options, full, args.locale, args.req) : null;
202
+ } catch {
203
+ return null;
204
+ }
205
+ }
206
+ /**
207
+ * A published form ready to render: sections, choices from sources resolved with their
208
+ * attributes, words filled from the defaults, the WhatsApp number when WhatsApp is on, and none of
209
+ * its other delivery settings. Null for unknown or unpublished slugs.
210
+ */ export async function getPublicForm(payload, slug, args = {}) {
211
+ const loaded = await loadPublishedForm(payload, slug, args);
212
+ return loaded ? loaded.form : null;
213
+ }
214
+
215
+ //# sourceMappingURL=forms.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/server/forms.ts"],"sourcesContent":["import type { Payload, PayloadRequest, TypedLocale, Where } from 'payload'\n\nimport { createLocalReq } from 'payload'\n\nimport type { ChoiceOption, FormSection, InputBlock, PublicForm } from '../shared/types.js'\nimport type { ResolvedOptions } from '../types.js'\n\nimport { isChoiceBlock, isInputBlock } from '../shared/rules.js'\nimport { fillWords } from '../shared/words.js'\n\nexport type EmailOutput = {\n bcc?: null | string\n body?: null | string\n cc?: null | string\n enabled?: boolean | null\n from?: null | string\n id?: null | string\n kind?: 'team' | 'visitor' | null\n label?: null | string\n replyToField?: null | string\n subject?: null | string\n to?: null | string\n visitorField?: null | string\n}\n\nexport type WebhookOutput = {\n enabled?: boolean | null\n headers?: Array<{ key?: null | string; value?: null | string }> | null\n id?: null | string\n label?: null | string\n method?: 'PATCH' | 'POST' | 'PUT' | null\n payloadMode?: 'answersOnly' | 'full' | null\n retries?: null | number\n signingSecret?: null | string\n timeoutMs?: null | number\n url?: null | string\n}\n\nexport type FormDelivery = {\n emails: EmailOutput[]\n store: boolean\n webhooks: WebhookOutput[]\n whatsapp: { enabled: boolean; messageTemplate?: null | string; number?: null | string; rowTemplate?: null | string }\n}\n\nexport type LoadedForm = {\n delivery: FormDelivery\n form: PublicForm\n}\n\ntype RawForm = {\n _status?: string\n delivery?: null | Partial<FormDelivery>\n id: number | string\n intakeState?: 'closed' | 'open' | null\n presentationMode?: 'singlePage' | 'steps' | null\n sections?: FormSection[] | null\n slug?: null | string\n successRedirectURL?: null | string\n successType?: 'message' | 'redirect' | null\n title?: null | string\n wording?: null | Record<string, null | string>\n}\n\ntype Locale = 'all' | TypedLocale | undefined\n\n/** The plugin options of an app, kept on its config by `formsPlugin`. */\nexport function getPluginOptions(payload: Payload): ResolvedOptions {\n const options = (payload.config.custom as { payloadForms?: ResolvedOptions } | undefined)?.payloadForms\n if (!options) {\n throw new Error('payload-dynamic-forms: formsPlugin is not installed (or is switched off) in this config.')\n }\n return options\n}\n\nfunction pick(doc: Record<string, unknown>, names: string[] = []): Record<string, unknown> {\n const out: Record<string, unknown> = {}\n for (const name of names) {\n if (doc[name] !== undefined && doc[name] !== null) {\n out[name] = doc[name]\n }\n }\n return out\n}\n\n/**\n * Turns source-based choices into `{ label, value, attributes }`: picked items in their order, or\n * every published item in the source's default sort when the pick is empty. Unpublished and\n * missing items are left out.\n */\nasync function resolveChoices(payload: Payload, options: ResolvedOptions, sections: FormSection[], locale: Locale) {\n const sources = options.optionSources || {}\n for (const section of sections) {\n for (const field of section.fields || []) {\n if (!isInputBlock(field) || !isChoiceBlock(field)) {\n continue\n }\n const block = field as { sourceItems?: unknown } & InputBlock\n const sourceKey = block.optionsFrom\n const source = sourceKey && sourceKey !== 'typed' ? sources[sourceKey] : undefined\n const picked = Array.isArray(block.sourceItems) ? block.sourceItems : []\n delete block.sourceItems\n if (!source) {\n block.options = (block.options || []).map((o: ChoiceOption) => ({ label: o.label, value: o.value }))\n continue\n }\n const slug = String(source.collection)\n const config = payload.collections[slug as never]?.config\n if (!config) {\n block.options = []\n continue\n }\n const ids = picked\n .filter((item: { relationTo?: string }) => item && item.relationTo === slug)\n .map((item: { value: unknown }) =>\n item.value && typeof item.value === 'object' ? (item.value as { id: number | string }).id : item.value,\n ) as Array<number | string>\n const and: Where[] = []\n if (picked.length) {\n and.push({ id: { in: ids.length ? ids : [-1] } })\n }\n if (config.versions?.drafts) {\n and.push({ _status: { equals: 'published' } })\n }\n // Trusted read of the items the editor picked: only label, value and listed attributes leave it.\n const { docs } = await payload.find({\n collection: slug as never,\n depth: 0,\n draft: false,\n locale: locale as never,\n overrideAccess: true,\n pagination: false,\n where: and.length ? { and } : undefined,\n })\n const byId = new Map(docs.map((doc) => [String((doc as { id: unknown }).id), doc as Record<string, unknown>]))\n const ordered = picked.length\n ? ids.map((id) => byId.get(String(id))).filter((d): d is Record<string, unknown> => Boolean(d))\n : (docs as Record<string, unknown>[])\n block.options = ordered\n .map((doc) => ({\n attributes: pick(doc, source.attributes),\n label: String(doc[source.labelField] ?? ''),\n value: String(doc[source.valueField] ?? ''),\n }))\n .filter((o) => o.value)\n }\n }\n}\n\nasync function buildLoaded(\n payload: Payload,\n options: ResolvedOptions,\n raw: RawForm,\n locale: Locale,\n req?: PayloadRequest,\n): Promise<LoadedForm> {\n const sections = structuredClone(raw.sections || []).filter(Boolean)\n await resolveChoices(payload, options, sections, locale)\n const d = raw.delivery || {}\n const delivery: FormDelivery = {\n emails: (d.emails || []).filter(Boolean),\n store: d.store !== false,\n webhooks: (d.webhooks || []).filter(Boolean),\n whatsapp: { enabled: false, ...(d.whatsapp || {}) },\n }\n const sends =\n delivery.emails.some((e) => e.enabled !== false) || delivery.webhooks.some((w) => w.enabled !== false)\n const form: PublicForm = {\n id: raw.id,\n slug: String(raw.slug || ''),\n honeypotField: options.honeypotField,\n intakeState: raw.intakeState === 'closed' ? 'closed' : 'open',\n presentationMode: raw.presentationMode === 'steps' ? 'steps' : 'singlePage',\n sections,\n spamChecks: delivery.store || sends,\n successRedirectURL: raw.successType === 'redirect' ? raw.successRedirectURL || null : null,\n successType: raw.successType === 'redirect' ? 'redirect' : 'message',\n title: String(raw.title || ''),\n turnstile: { enabled: options.turnstile.enabled, fieldName: options.turnstile.fieldName },\n wording: fillWords(raw.wording as never, options.words),\n }\n if (delivery.whatsapp.enabled) {\n let number = delivery.whatsapp.number?.trim() || ''\n if (!number && options.whatsapp?.number) {\n const request = req || (await createLocalReq({}, payload))\n number = String((await options.whatsapp.number({ form, req: request })) || '').trim()\n }\n if (number) {\n form.whatsapp = { number }\n }\n }\n return { delivery, form }\n}\n\n/**\n * Loads a published form by slug, as a visitor would see it, plus its delivery settings for the\n * server. Null when the slug is unknown or the form is not published.\n */\nexport async function loadPublishedForm(\n payload: Payload,\n slug: string,\n args: { locale?: Locale; req?: PayloadRequest } = {},\n): Promise<LoadedForm | null> {\n const options = getPluginOptions(payload)\n // Read as an anonymous visitor: access control decides, delivery fields are stripped.\n const { docs } = await payload.find({\n collection: options.slugs.forms as never,\n depth: 0,\n draft: false,\n limit: 1,\n locale: args.locale as never,\n overrideAccess: false,\n pagination: false,\n where: { and: [{ slug: { equals: slug } }, { _status: { equals: 'published' } }] },\n })\n const visible = docs[0] as RawForm | undefined\n if (!visible) {\n return null\n }\n // Server-side read of the same published document for its delivery settings and secrets.\n const full = (await payload.findByID({\n id: visible.id,\n collection: options.slugs.forms as never,\n depth: 0,\n draft: false,\n locale: args.locale as never,\n overrideAccess: true,\n })) as unknown as RawForm\n return buildLoaded(payload, options, { ...full, id: visible.id }, args.locale, args.req)\n}\n\n/** Loads a form by id with its delivery settings (trusted server use: resend). */\nexport async function loadFormById(\n payload: Payload,\n id: number | string,\n args: { locale?: Locale; req?: PayloadRequest } = {},\n): Promise<LoadedForm | null> {\n const options = getPluginOptions(payload)\n try {\n const full = (await payload.findByID({\n id,\n collection: options.slugs.forms as never,\n depth: 0,\n draft: false,\n locale: args.locale as never,\n overrideAccess: true,\n })) as unknown as RawForm\n return full ? buildLoaded(payload, options, full, args.locale, args.req) : null\n } catch {\n return null\n }\n}\n\n/**\n * A published form ready to render: sections, choices from sources resolved with their\n * attributes, words filled from the defaults, the WhatsApp number when WhatsApp is on, and none of\n * its other delivery settings. Null for unknown or unpublished slugs.\n */\nexport async function getPublicForm(\n payload: Payload,\n slug: string,\n args: { locale?: Locale; req?: PayloadRequest } = {},\n): Promise<null | PublicForm> {\n const loaded = await loadPublishedForm(payload, slug, args)\n return loaded ? loaded.form : null\n}\n"],"names":["createLocalReq","isChoiceBlock","isInputBlock","fillWords","getPluginOptions","payload","options","config","custom","payloadForms","Error","pick","doc","names","out","name","undefined","resolveChoices","sections","locale","sources","optionSources","section","field","fields","block","sourceKey","optionsFrom","source","picked","Array","isArray","sourceItems","map","o","label","value","slug","String","collection","collections","ids","filter","item","relationTo","id","and","length","push","in","versions","drafts","_status","equals","docs","find","depth","draft","overrideAccess","pagination","where","byId","Map","ordered","get","d","Boolean","attributes","labelField","valueField","buildLoaded","raw","req","structuredClone","delivery","emails","store","webhooks","whatsapp","enabled","sends","some","e","w","form","honeypotField","intakeState","presentationMode","spamChecks","successRedirectURL","successType","title","turnstile","fieldName","wording","words","number","trim","request","loadPublishedForm","args","slugs","forms","limit","visible","full","findByID","loadFormById","getPublicForm","loaded"],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import type { Payload, PayloadRequest } from 'payload';
2
+ import type { Answers, SnapshotRow, SubmissionStatus } from '../shared/types.js';
3
+ import { type LogEntry } from '../delivery/run.js';
4
+ export type SubmissionDoc = {
5
+ answers?: Answers | null;
6
+ attachments?: Array<{
7
+ id: number | string;
8
+ } | number | string> | null;
9
+ deliveryLog?: LogEntry[] | null;
10
+ form?: {
11
+ id: number | string;
12
+ } | null | number | string;
13
+ id: number | string;
14
+ metadata?: {
15
+ locale?: null | string;
16
+ } | null;
17
+ snapshot?: null | SnapshotRow[];
18
+ };
19
+ /**
20
+ * Runs again the outputs whose last try failed and that still exist on the form. Outputs removed
21
+ * since get a "skipped" entry. Returns the full new log and status (the caller saves them).
22
+ */
23
+ export declare function resendFailedDeliveries(payload: Payload, submission: SubmissionDoc, req: PayloadRequest): Promise<{
24
+ added: LogEntry[];
25
+ log: LogEntry[];
26
+ status: SubmissionStatus;
27
+ }>;
@@ -0,0 +1,84 @@
1
+ import { runDeliveries, statusFromLog } from '../delivery/run.js';
2
+ import { getPluginOptions, loadFormById } from './forms.js';
3
+ /**
4
+ * Runs again the outputs whose last try failed and that still exist on the form. Outputs removed
5
+ * since get a "skipped" entry. Returns the full new log and status (the caller saves them).
6
+ */ export async function resendFailedDeliveries(payload, submission, req) {
7
+ const options = getPluginOptions(payload);
8
+ const log = (submission.deliveryLog || []).map((entry)=>{
9
+ const { id: _id, ...rest } = entry;
10
+ return rest;
11
+ });
12
+ const formId = typeof submission.form === 'object' && submission.form ? submission.form.id : submission.form;
13
+ const loaded = formId != null ? await loadFormById(payload, formId, {
14
+ locale: submission.metadata?.locale || undefined,
15
+ req
16
+ }) : null;
17
+ const last = new Map();
18
+ for (const entry of log){
19
+ if (entry.outputId) {
20
+ last.set(entry.outputId, entry);
21
+ }
22
+ }
23
+ const failed = [
24
+ ...last.values()
25
+ ].filter((entry)=>entry.result === 'failed');
26
+ if (!failed.length) {
27
+ return {
28
+ added: [],
29
+ log,
30
+ status: statusFromLog(log)
31
+ };
32
+ }
33
+ const existing = new Set(loaded ? [
34
+ ...loaded.delivery.emails,
35
+ ...loaded.delivery.webhooks
36
+ ].filter((o)=>o.enabled !== false && o.id).map((o)=>String(o.id)) : []);
37
+ const added = [];
38
+ const rerun = new Set();
39
+ for (const entry of failed){
40
+ if (existing.has(entry.outputId)) {
41
+ rerun.add(entry.outputId);
42
+ } else {
43
+ added.push({
44
+ at: new Date().toISOString(),
45
+ error: 'This output is no longer on the form (or is switched off); not sent again.',
46
+ kind: entry.kind,
47
+ label: entry.label,
48
+ outputId: entry.outputId,
49
+ result: 'skipped',
50
+ target: entry.target,
51
+ tries: 0
52
+ });
53
+ }
54
+ }
55
+ if (loaded && rerun.size) {
56
+ const attachments = (submission.attachments || []).map((a)=>typeof a === 'object' ? a.id : a);
57
+ added.push(...await runDeliveries({
58
+ answers: submission.answers || {},
59
+ attachments: attachments.map((id)=>({
60
+ id,
61
+ fieldKey: '',
62
+ filename: ''
63
+ })),
64
+ delivery: loaded.delivery,
65
+ form: loaded.form,
66
+ options,
67
+ payload,
68
+ req,
69
+ snapshot: submission.snapshot || [],
70
+ submissionId: submission.id
71
+ }, rerun));
72
+ }
73
+ const next = [
74
+ ...log,
75
+ ...added
76
+ ];
77
+ return {
78
+ added,
79
+ log: next,
80
+ status: statusFromLog(next)
81
+ };
82
+ }
83
+
84
+ //# sourceMappingURL=resend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/server/resend.ts"],"sourcesContent":["import type { Payload, PayloadRequest } from 'payload'\n\nimport type { Answers, SnapshotRow, SubmissionStatus } from '../shared/types.js'\n\nimport { type LogEntry, runDeliveries, statusFromLog } from '../delivery/run.js'\nimport { getPluginOptions, loadFormById } from './forms.js'\n\nexport type SubmissionDoc = {\n answers?: Answers | null\n attachments?: Array<{ id: number | string } | number | string> | null\n deliveryLog?: LogEntry[] | null\n form?: { id: number | string } | null | number | string\n id: number | string\n metadata?: { locale?: null | string } | null\n snapshot?: null | SnapshotRow[]\n}\n\n/**\n * Runs again the outputs whose last try failed and that still exist on the form. Outputs removed\n * since get a \"skipped\" entry. Returns the full new log and status (the caller saves them).\n */\nexport async function resendFailedDeliveries(\n payload: Payload,\n submission: SubmissionDoc,\n req: PayloadRequest,\n): Promise<{ added: LogEntry[]; log: LogEntry[]; status: SubmissionStatus }> {\n const options = getPluginOptions(payload)\n const log = (submission.deliveryLog || []).map((entry) => {\n const { id: _id, ...rest } = entry as { id?: string } & LogEntry\n return rest as LogEntry\n })\n const formId = typeof submission.form === 'object' && submission.form ? submission.form.id : submission.form\n const loaded = formId != null ? await loadFormById(payload, formId, { locale: (submission.metadata?.locale || undefined) as never, req }) : null\n const last = new Map<string, LogEntry>()\n for (const entry of log) {\n if (entry.outputId) {\n last.set(entry.outputId, entry)\n }\n }\n const failed = [...last.values()].filter((entry) => entry.result === 'failed')\n if (!failed.length) {\n return { added: [], log, status: statusFromLog(log) }\n }\n const existing = new Set<string>(\n loaded\n ? [...loaded.delivery.emails, ...loaded.delivery.webhooks]\n .filter((o) => o.enabled !== false && o.id)\n .map((o) => String(o.id))\n : [],\n )\n const added: LogEntry[] = []\n const rerun = new Set<string>()\n for (const entry of failed) {\n if (existing.has(entry.outputId!)) {\n rerun.add(entry.outputId!)\n } else {\n added.push({\n at: new Date().toISOString(),\n error: 'This output is no longer on the form (or is switched off); not sent again.',\n kind: entry.kind,\n label: entry.label,\n outputId: entry.outputId,\n result: 'skipped',\n target: entry.target,\n tries: 0,\n })\n }\n }\n if (loaded && rerun.size) {\n const attachments = (submission.attachments || []).map((a) => (typeof a === 'object' ? a.id : a))\n added.push(\n ...(await runDeliveries(\n {\n answers: submission.answers || {},\n attachments: attachments.map((id) => ({ id, fieldKey: '', filename: '' })),\n delivery: loaded.delivery,\n form: loaded.form,\n options,\n payload,\n req,\n snapshot: submission.snapshot || [],\n submissionId: submission.id,\n },\n rerun,\n )),\n )\n }\n const next = [...log, ...added]\n return { added, log: next, status: statusFromLog(next) }\n}\n"],"names":["runDeliveries","statusFromLog","getPluginOptions","loadFormById","resendFailedDeliveries","payload","submission","req","options","log","deliveryLog","map","entry","id","_id","rest","formId","form","loaded","locale","metadata","undefined","last","Map","outputId","set","failed","values","filter","result","length","added","status","existing","Set","delivery","emails","webhooks","o","enabled","String","rerun","has","add","push","at","Date","toISOString","error","kind","label","target","tries","size","attachments","a","answers","fieldKey","filename","snapshot","submissionId","next"],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ import type { Answers, AnswerValue, FieldTypeChecks, FileLike, FormLike, FormWords, InputBlock, SnapshotRow, YesNoWords } from './types.js';
2
+ /** Default limits for file-upload fields. */
3
+ export declare const DEFAULT_FILE_LIMITS: {
4
+ readonly maxFileSize: number;
5
+ readonly maxTotalSize: number;
6
+ readonly mimeTypes: readonly ["application/pdf", "image/jpeg", "image/png", "image/webp", "text/plain", "text/csv", "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"];
7
+ };
8
+ export type FileLimits = {
9
+ maxFileSize?: number;
10
+ maxTotalSize?: number;
11
+ mimeTypes?: readonly string[];
12
+ };
13
+ /** One valid address: no spaces, commas or lists, a dot in the domain. */
14
+ export declare function isValidEmail(value: unknown): boolean;
15
+ /** Turns a raw answer (form data or JSON) into the value the field type keeps. */
16
+ export declare function normalizeAnswer(field: InputBlock, raw: unknown, fieldTypes?: Record<string, FieldTypeChecks>): AnswerValue;
17
+ /**
18
+ * The text of an answer in messages and snapshots: option labels, file names, and the form's
19
+ * `yes` / `no` words for a checkbox (pass `fillWords(form.wording)` or the public form's wording).
20
+ */
21
+ export declare function answerToText(field: InputBlock, value: AnswerValue, words: YesNoWords, fieldTypes?: Record<string, FieldTypeChecks>): string;
22
+ /** Checks the files given to one upload field: count, MIME type and size. */
23
+ export declare function checkFiles(field: InputBlock, files: FileLike[], limits?: FileLimits): boolean;
24
+ export type CheckOptions = {
25
+ /** Checks for app-registered field types. */
26
+ fieldTypes?: Record<string, FieldTypeChecks>;
27
+ /** Files per upload field key. */
28
+ files?: Record<string, FileLike[]>;
29
+ limits?: FileLimits;
30
+ /** Form words; default: the form's wording filled from the defaults. */
31
+ words?: FormWords;
32
+ };
33
+ export type CheckResult = {
34
+ /** Shown answers only, normalised. Upload fields are left out (the server adds them). */
35
+ answers: Answers;
36
+ fieldErrors: Record<string, string>;
37
+ /** Keys of shown upload fields that got files. */
38
+ fileKeys: string[];
39
+ ok: boolean;
40
+ };
41
+ /**
42
+ * Normalises raw answers, drops hidden ones (and, in steps, those of sections off the path), and
43
+ * reports a field error for every shown field that is required and empty or has a bad value.
44
+ */
45
+ export declare function checkAnswers(form: FormLike, raw: Record<string, unknown>, options?: CheckOptions): CheckResult;
46
+ /** Section title, label and value per answered, shown field, as worded now. */
47
+ export declare function buildSnapshot(form: FormLike, answers: Answers, fieldTypes?: Record<string, FieldTypeChecks>): SnapshotRow[];