@actuate-media/cms-core 0.31.0 → 0.33.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 (84) hide show
  1. package/dist/__tests__/api/form-webhooks.test.d.ts +2 -0
  2. package/dist/__tests__/api/form-webhooks.test.d.ts.map +1 -0
  3. package/dist/__tests__/api/form-webhooks.test.js +162 -0
  4. package/dist/__tests__/api/form-webhooks.test.js.map +1 -0
  5. package/dist/__tests__/api/public-forms.test.d.ts +2 -0
  6. package/dist/__tests__/api/public-forms.test.d.ts.map +1 -0
  7. package/dist/__tests__/api/public-forms.test.js +125 -0
  8. package/dist/__tests__/api/public-forms.test.js.map +1 -0
  9. package/dist/__tests__/forms/notify.test.d.ts +2 -0
  10. package/dist/__tests__/forms/notify.test.d.ts.map +1 -0
  11. package/dist/__tests__/forms/notify.test.js +114 -0
  12. package/dist/__tests__/forms/notify.test.js.map +1 -0
  13. package/dist/__tests__/forms/spam.test.d.ts +2 -0
  14. package/dist/__tests__/forms/spam.test.d.ts.map +1 -0
  15. package/dist/__tests__/forms/spam.test.js +115 -0
  16. package/dist/__tests__/forms/spam.test.js.map +1 -0
  17. package/dist/__tests__/forms/submission-files.test.d.ts +2 -0
  18. package/dist/__tests__/forms/submission-files.test.d.ts.map +1 -0
  19. package/dist/__tests__/forms/submission-files.test.js +65 -0
  20. package/dist/__tests__/forms/submission-files.test.js.map +1 -0
  21. package/dist/__tests__/forms/submission-pipeline.test.d.ts +2 -0
  22. package/dist/__tests__/forms/submission-pipeline.test.d.ts.map +1 -0
  23. package/dist/__tests__/forms/submission-pipeline.test.js +131 -0
  24. package/dist/__tests__/forms/submission-pipeline.test.js.map +1 -0
  25. package/dist/__tests__/forms/submission-validation.test.d.ts +2 -0
  26. package/dist/__tests__/forms/submission-validation.test.d.ts.map +1 -0
  27. package/dist/__tests__/forms/submission-validation.test.js +108 -0
  28. package/dist/__tests__/forms/submission-validation.test.js.map +1 -0
  29. package/dist/__tests__/forms/webhooks.test.d.ts +2 -0
  30. package/dist/__tests__/forms/webhooks.test.d.ts.map +1 -0
  31. package/dist/__tests__/forms/webhooks.test.js +203 -0
  32. package/dist/__tests__/forms/webhooks.test.js.map +1 -0
  33. package/dist/api/handler-factory.d.ts.map +1 -1
  34. package/dist/api/handler-factory.js +2 -2
  35. package/dist/api/handler-factory.js.map +1 -1
  36. package/dist/api/handlers.d.ts.map +1 -1
  37. package/dist/api/handlers.js +420 -56
  38. package/dist/api/handlers.js.map +1 -1
  39. package/dist/api/router.d.ts +2 -1
  40. package/dist/api/router.d.ts.map +1 -1
  41. package/dist/api/router.js +3 -0
  42. package/dist/api/router.js.map +1 -1
  43. package/dist/config/types.d.ts +11 -6
  44. package/dist/config/types.d.ts.map +1 -1
  45. package/dist/forms/entries.d.ts.map +1 -1
  46. package/dist/forms/entries.js +16 -1
  47. package/dist/forms/entries.js.map +1 -1
  48. package/dist/forms/files.d.ts +38 -0
  49. package/dist/forms/files.d.ts.map +1 -0
  50. package/dist/forms/files.js +134 -0
  51. package/dist/forms/files.js.map +1 -0
  52. package/dist/forms/index.d.ts +6 -0
  53. package/dist/forms/index.d.ts.map +1 -1
  54. package/dist/forms/index.js +6 -0
  55. package/dist/forms/index.js.map +1 -1
  56. package/dist/forms/notify.d.ts +37 -0
  57. package/dist/forms/notify.d.ts.map +1 -0
  58. package/dist/forms/notify.js +198 -0
  59. package/dist/forms/notify.js.map +1 -0
  60. package/dist/forms/spam.d.ts +33 -0
  61. package/dist/forms/spam.d.ts.map +1 -0
  62. package/dist/forms/spam.js +93 -0
  63. package/dist/forms/spam.js.map +1 -0
  64. package/dist/forms/submission.d.ts +116 -0
  65. package/dist/forms/submission.d.ts.map +1 -0
  66. package/dist/forms/submission.js +336 -0
  67. package/dist/forms/submission.js.map +1 -0
  68. package/dist/forms/validation.d.ts +19 -0
  69. package/dist/forms/validation.d.ts.map +1 -0
  70. package/dist/forms/validation.js +235 -0
  71. package/dist/forms/validation.js.map +1 -0
  72. package/dist/forms/webhooks.d.ts +54 -0
  73. package/dist/forms/webhooks.d.ts.map +1 -0
  74. package/dist/forms/webhooks.js +292 -0
  75. package/dist/forms/webhooks.js.map +1 -0
  76. package/dist/index.d.ts +2 -2
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js +1 -1
  79. package/dist/index.js.map +1 -1
  80. package/dist/security/client-ip.d.ts +11 -21
  81. package/dist/security/client-ip.d.ts.map +1 -1
  82. package/dist/security/client-ip.js +38 -0
  83. package/dist/security/client-ip.js.map +1 -1
  84. package/package.json +1 -1
@@ -0,0 +1,336 @@
1
+ /**
2
+ * Public submission pipeline.
3
+ *
4
+ * Orchestrates the full lifecycle for an incoming public submission:
5
+ * resolve schema → validate + normalize → sanitize → score spam →
6
+ * derive sender/preview → assign entry number (transaction) → persist →
7
+ * attach files → fire plugin hooks → audit log → build confirmation.
8
+ *
9
+ * The HTTP layer (`api/handlers.ts`) is responsible only for parsing the
10
+ * request (JSON or multipart), resolving the form, rate-limiting, CORS, and
11
+ * optional CAPTCHA verification. All trust decisions live here so the same
12
+ * pipeline backs every entry point (public slug route, legacy id route, and
13
+ * the future page-builder block).
14
+ */
15
+ import { getDB } from '../db.js';
16
+ import { logEvent } from '../security/audit.js';
17
+ import { hashClientIp } from '../security/client-ip.js';
18
+ import { getActuateConfig } from '../config/runtime.js';
19
+ import { getSchemaVersion } from './service.js';
20
+ import { validateAndNormalizeSubmission } from './validation.js';
21
+ import { scoreSubmissionSpam } from './spam.js';
22
+ import { persistSubmissionFiles } from './files.js';
23
+ import { sendFormNotifications } from './notify.js';
24
+ import { dispatchFormWebhooks } from './webhooks.js';
25
+ /** Thrown when field validation fails; mapped to HTTP 422 by the API layer. */
26
+ export class SubmissionValidationError extends Error {
27
+ fieldErrors;
28
+ constructor(fieldErrors) {
29
+ super('Submission validation failed');
30
+ this.name = 'SubmissionValidationError';
31
+ this.fieldErrors = fieldErrors;
32
+ }
33
+ }
34
+ const DEFAULT_SUCCESS_MESSAGE = 'Thank you! Your submission has been received.';
35
+ function asString(value) {
36
+ if (typeof value === 'string')
37
+ return value;
38
+ if (typeof value === 'number' || typeof value === 'boolean')
39
+ return String(value);
40
+ return '';
41
+ }
42
+ /** Resolve the field schema the submission should be validated against. */
43
+ async function resolveFields(form) {
44
+ if (form.activeSchemaVersionId) {
45
+ const version = await getSchemaVersion(form.activeSchemaVersionId);
46
+ if (version && version.fields.length > 0) {
47
+ return { fields: version.fields, versionId: version.id };
48
+ }
49
+ }
50
+ return { fields: form.fields ?? [], versionId: form.activeSchemaVersionId ?? null };
51
+ }
52
+ function deriveSender(fields, data) {
53
+ let name = null;
54
+ let email = null;
55
+ let phone = null;
56
+ for (const field of fields) {
57
+ const v = asString(data[field.key]).trim();
58
+ if (!v)
59
+ continue;
60
+ if (!email && field.type === 'email')
61
+ email = v;
62
+ if (!phone && field.type === 'phone')
63
+ phone = v;
64
+ if (!name && /name/i.test(field.key))
65
+ name = v;
66
+ }
67
+ // Fallbacks by common key naming when the schema didn't use typed fields.
68
+ if (!email)
69
+ email = firstByKeys(data, ['email', 'emailaddress', 'email_address']);
70
+ if (!name)
71
+ name = firstByKeys(data, ['name', 'fullname', 'full_name', 'firstname', 'first_name']);
72
+ if (!phone)
73
+ phone = firstByKeys(data, ['phone', 'phonenumber', 'phone_number', 'tel']);
74
+ return { name, email, phone };
75
+ }
76
+ function firstByKeys(data, candidates) {
77
+ const lower = new Map(Object.entries(data).map(([k, v]) => [k.toLowerCase(), v]));
78
+ for (const c of candidates) {
79
+ const v = asString(lower.get(c)).trim();
80
+ if (v)
81
+ return v;
82
+ }
83
+ return null;
84
+ }
85
+ function derivePreview(fields, data) {
86
+ // Prefer long-form fields, then any non-empty string value.
87
+ for (const field of fields) {
88
+ if (field.type === 'textarea') {
89
+ const v = asString(data[field.key]).trim();
90
+ if (v)
91
+ return v.slice(0, 200);
92
+ }
93
+ }
94
+ const messageKeys = ['message', 'body', 'comments', 'notes', 'description'];
95
+ const byKey = firstByKeys(data, messageKeys);
96
+ if (byKey)
97
+ return byKey.slice(0, 200);
98
+ for (const v of Object.values(data)) {
99
+ const s = asString(v).trim();
100
+ if (s)
101
+ return s.slice(0, 200);
102
+ }
103
+ return null;
104
+ }
105
+ /** Site-wide default spam strategies; per-form strategies are merged in `scoreSubmissionSpam`. */
106
+ function defaultSpamStrategies() {
107
+ return (getActuateConfig()?.forms?.defaultSpamProtection ?? []);
108
+ }
109
+ async function assignAndCreate(db, row, formId) {
110
+ // Sequential per-form entry number assigned inside a transaction so two
111
+ // concurrent submissions don't read the same max. Not globally unique by DB
112
+ // constraint, but the transaction makes collisions vanishingly unlikely.
113
+ if (typeof db.$transaction === 'function') {
114
+ return db.$transaction(async (tx) => {
115
+ const agg = await tx.formSubmission.aggregate({
116
+ where: { formId },
117
+ _max: { entryNumber: true },
118
+ });
119
+ const entryNumber = (agg?._max?.entryNumber ?? 0) + 1;
120
+ const created = await tx.formSubmission.create({
121
+ data: { ...row, entryNumber },
122
+ select: { id: true, entryNumber: true },
123
+ });
124
+ return { id: String(created.id), entryNumber: created.entryNumber ?? entryNumber };
125
+ });
126
+ }
127
+ const created = await db.formSubmission.create({
128
+ data: row,
129
+ select: { id: true, entryNumber: true },
130
+ });
131
+ return { id: String(created.id), entryNumber: created.entryNumber ?? null };
132
+ }
133
+ function buildConfirmation(form) {
134
+ if (form.redirectUrl && form.redirectUrl.trim()) {
135
+ return { type: 'redirect', redirectUrl: form.redirectUrl.trim() };
136
+ }
137
+ const message = form.successMessage?.trim() ||
138
+ getActuateConfig()?.forms?.defaultSuccessMessage ||
139
+ DEFAULT_SUCCESS_MESSAGE;
140
+ return { type: 'message', message };
141
+ }
142
+ async function fireSubmissionHooks(formId, data) {
143
+ try {
144
+ const config = getActuateConfig();
145
+ const hooks = (config?._pluginHooks ?? []);
146
+ for (const hook of hooks.filter((h) => h?.event === 'afterCreate:form-submissions')) {
147
+ await hook.handler({ formId, data });
148
+ }
149
+ }
150
+ catch (err) {
151
+ if (process.env.NODE_ENV !== 'test') {
152
+ console.error('[Actuate CMS] Form submission hook error:', err);
153
+ }
154
+ }
155
+ }
156
+ /**
157
+ * Best-effort notification + webhook fan-out after a submission is persisted.
158
+ * Exported so the API layer (or tests) can await it deterministically; the
159
+ * pipeline itself fires it without blocking the public response. Every branch
160
+ * is wrapped so a single failing channel can't take down the others.
161
+ */
162
+ export async function runSubmissionSideEffects(input) {
163
+ const { form, fields, data, spamStatus } = input;
164
+ const webhookEvent = spamStatus === 'spam' ? 'form.spam_detected' : 'form.submitted';
165
+ // Plugin hooks + admin email + autoresponder: clean submissions only.
166
+ if (spamStatus !== 'spam') {
167
+ await fireSubmissionHooks(form.id, data).catch(() => { });
168
+ const emailCtx = {
169
+ formId: form.id,
170
+ formName: form.name,
171
+ entryNumber: input.entryNumber,
172
+ submittedAt: input.submittedAt,
173
+ fields,
174
+ data,
175
+ senderName: input.sender.name,
176
+ senderEmail: input.sender.email,
177
+ };
178
+ await sendFormNotifications(form.notificationSettings, emailCtx).catch((err) => {
179
+ if (process.env.NODE_ENV !== 'test') {
180
+ console.error('[Actuate CMS] Form notification error:', err);
181
+ }
182
+ });
183
+ }
184
+ // Webhooks fire for both clean and spam submissions (subscribers opt in per event).
185
+ await dispatchFormWebhooks(form.id, {
186
+ event: webhookEvent,
187
+ formId: form.id,
188
+ formSlug: input.formSlug,
189
+ submissionId: input.submissionId,
190
+ entryNumber: input.entryNumber,
191
+ data,
192
+ }).catch((err) => {
193
+ if (process.env.NODE_ENV !== 'test') {
194
+ console.error('[Actuate CMS] Form webhook dispatch error:', err);
195
+ }
196
+ });
197
+ }
198
+ /**
199
+ * Process a validated request into a persisted submission. Throws
200
+ * `SubmissionValidationError` when field validation fails so the caller can
201
+ * return a structured 422.
202
+ */
203
+ export async function processSubmission(form, ctx) {
204
+ const db = getDB();
205
+ const { fields, versionId } = await resolveFields(form);
206
+ const fileKeys = new Set((ctx.files ?? []).map((f) => f.fieldKey));
207
+ const validation = validateAndNormalizeSubmission(fields, ctx.values, { fileKeys });
208
+ if (!validation.ok) {
209
+ throw new SubmissionValidationError(validation.errors);
210
+ }
211
+ const sender = deriveSender(fields, validation.data);
212
+ const spam = scoreSubmissionSpam({
213
+ fields,
214
+ rawValues: ctx.values,
215
+ settings: form.spamSettings,
216
+ defaultStrategies: defaultSpamStrategies(),
217
+ senderEmail: sender.email,
218
+ elapsedMs: ctx.elapsedMs ?? null,
219
+ origin: ctx.origin ?? null,
220
+ referrer: ctx.referrer ?? null,
221
+ });
222
+ const attribution = ctx.attribution && typeof ctx.attribution === 'object' ? ctx.attribution : null;
223
+ const row = {
224
+ formId: form.id,
225
+ schemaVersionId: versionId,
226
+ data: validation.data,
227
+ normalizedData: validation.normalized,
228
+ attribution,
229
+ senderName: sender.name,
230
+ senderEmail: sender.email,
231
+ senderPhone: sender.phone,
232
+ preview: derivePreview(fields, validation.data),
233
+ spamStatus: spam.status,
234
+ unread: true,
235
+ ipAddressHash: hashClientIp(ctx.ip ?? 'unknown'),
236
+ userAgent: ctx.userAgent ?? null,
237
+ referrer: ctx.referrer ?? null,
238
+ pageUrl: ctx.pageUrl ?? null,
239
+ utmSource: asString(attribution?.source) || null,
240
+ utmMedium: asString(attribution?.medium) || null,
241
+ utmCampaign: asString(attribution?.campaign) || null,
242
+ submittedAt: new Date(),
243
+ };
244
+ const { id, entryNumber } = await assignAndCreate(db, row, form.id);
245
+ if ((ctx.files?.length ?? 0) > 0) {
246
+ try {
247
+ await persistSubmissionFiles(form.id, id, ctx.files ?? []);
248
+ }
249
+ catch (err) {
250
+ if (process.env.NODE_ENV !== 'test') {
251
+ console.error('[Actuate CMS] Failed to persist submission files:', err);
252
+ }
253
+ }
254
+ }
255
+ // Notification + webhook fan-out (admin email, autoresponder, signed
256
+ // webhooks). Fire-and-forget so a slow/failing channel never blocks or
257
+ // breaks the public response. Spam suppresses email but still fires
258
+ // `form.spam_detected` webhooks for subscribers.
259
+ void runSubmissionSideEffects({
260
+ form,
261
+ fields,
262
+ data: validation.data,
263
+ submissionId: id,
264
+ entryNumber,
265
+ spamStatus: spam.status,
266
+ sender: { name: sender.name, email: sender.email },
267
+ submittedAt: new Date().toISOString(),
268
+ formSlug: form.slug,
269
+ });
270
+ await logEvent({
271
+ event: spam.status === 'spam' ? 'form.spam_detected' : 'form.submitted',
272
+ details: {
273
+ formId: form.id,
274
+ submissionId: id,
275
+ entryNumber,
276
+ spamStatus: spam.status,
277
+ ...(spam.reasons.length > 0 ? { spamReasons: spam.reasons } : {}),
278
+ },
279
+ });
280
+ return {
281
+ submissionId: id,
282
+ entryNumber,
283
+ spamStatus: spam.status,
284
+ confirmation: buildConfirmation(form),
285
+ };
286
+ }
287
+ /**
288
+ * Project a form definition into the public, embeddable schema. Strips every
289
+ * server-only concern (access rules, notification recipients, webhook secrets,
290
+ * blocklists, encryption flags) so nothing sensitive leaks to the browser.
291
+ */
292
+ export function buildPublicForm(form, fields, schemaVersionId, options = {}) {
293
+ const publicFields = fields
294
+ .filter((f) => f.type !== 'honeypot')
295
+ .map((f) => ({
296
+ id: f.id,
297
+ key: f.key,
298
+ label: f.label,
299
+ type: f.type,
300
+ required: Boolean(f.required),
301
+ placeholder: f.placeholder,
302
+ helpText: f.helpText,
303
+ defaultValue: f.defaultValue ?? undefined,
304
+ options: f.options,
305
+ width: f.width,
306
+ sortOrder: f.sortOrder,
307
+ validationRules: f.validationRules
308
+ ? sanitizePublicRules(f.validationRules)
309
+ : undefined,
310
+ conditionalLogic: f.conditionalLogic,
311
+ }));
312
+ const basePath = getActuateConfig()?.forms?.publicSubmissionPath ?? '/api/cms/public/forms';
313
+ return {
314
+ id: form.id,
315
+ name: form.name,
316
+ slug: form.slug,
317
+ description: form.description || undefined,
318
+ fields: publicFields,
319
+ submitPath: `${basePath.replace(/\/$/, '')}/${form.slug}/submit`,
320
+ successMessage: form.successMessage,
321
+ redirectUrl: form.redirectUrl,
322
+ spam: {
323
+ honeypotFieldKey: form.spamSettings?.honeypotFieldKey ?? fields.find((f) => f.type === 'honeypot')?.key,
324
+ minSubmitSeconds: form.spamSettings?.minSubmitSeconds,
325
+ },
326
+ captcha: options.captcha && options.captcha.provider !== 'none' ? options.captcha : undefined,
327
+ schemaVersionId,
328
+ };
329
+ }
330
+ /** Strip server-side-only validation hints (custom validator names) before exposing rules. */
331
+ function sanitizePublicRules(rules) {
332
+ const { customValidator: _drop, ...rest } = rules;
333
+ return rest;
334
+ }
335
+ export { resolveFields as resolveSubmissionFields };
336
+ //# sourceMappingURL=submission.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"submission.js","sourceRoot":"","sources":["../../src/forms/submission.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import type { FormField } from './types.js';
2
+ export interface FieldError {
3
+ key: string;
4
+ message: string;
5
+ }
6
+ export interface SubmissionValidationResult {
7
+ ok: boolean;
8
+ errors: FieldError[];
9
+ /** Sanitized raw values keyed by field key (HTML stripped from strings). */
10
+ data: Record<string, unknown>;
11
+ /** Type-coerced values for export/search. */
12
+ normalized: Record<string, unknown>;
13
+ }
14
+ export interface ValidateOptions {
15
+ /** Field keys that have an accompanying uploaded file (for required-file checks). */
16
+ fileKeys?: ReadonlySet<string>;
17
+ }
18
+ export declare function validateAndNormalizeSubmission(fields: FormField[], rawValues: Record<string, unknown>, options?: ValidateOptions): SubmissionValidationResult;
19
+ //# sourceMappingURL=validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/forms/validation.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAmB,SAAS,EAAE,MAAM,YAAY,CAAA;AAE5D,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,0BAA0B;IACzC,EAAE,EAAE,OAAO,CAAA;IACX,MAAM,EAAE,UAAU,EAAE,CAAA;IACpB,4EAA4E;IAC5E,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC7B,6CAA6C;IAC7C,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACpC;AAED,MAAM,WAAW,eAAe;IAC9B,qFAAqF;IACrF,QAAQ,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;CAC/B;AAmED,wBAAgB,8BAA8B,CAC5C,MAAM,EAAE,SAAS,EAAE,EACnB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClC,OAAO,GAAE,eAAoB,GAC5B,0BAA0B,CAuJ5B"}
@@ -0,0 +1,235 @@
1
+ /**
2
+ * Server-side submission validation + normalization.
3
+ *
4
+ * This is the authoritative gate for incoming public form submissions: the
5
+ * client-side form is convenience only and is assumed hostile. Every value is
6
+ * validated against the field's declared type and `validationRules`, then split
7
+ * into two views:
8
+ *
9
+ * - `data` — the sanitized *raw* values (HTML-stripped strings) that we
10
+ * persist and render back in the admin inbox.
11
+ * - `normalized` — type-coerced values (numbers as numbers, checkboxes as
12
+ * booleans, dates as ISO strings) used for export/search.
13
+ *
14
+ * Keys that are not part of the schema are dropped entirely — a submitter
15
+ * cannot smuggle arbitrary columns into the stored payload. Fields hidden by
16
+ * conditional logic are skipped (not required, not stored).
17
+ */
18
+ import { stripHtml } from '../security/sanitize.js';
19
+ // Conservative hard caps so a single field can't be used to balloon the row /
20
+ // exhaust memory even when the schema omits explicit length rules.
21
+ const MAX_TEXT_LEN = 5_000;
22
+ const MAX_TEXTAREA_LEN = 50_000;
23
+ const MAX_MULTISELECT_ITEMS = 200;
24
+ const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
25
+ const PHONE_RE = /^[+]?[0-9\s().-]{6,20}$/;
26
+ const NON_INPUT = new Set(['richtext', 'divider', 'honeypot']);
27
+ const TRUTHY = new Set(['true', 'on', '1', 'yes', 'checked']);
28
+ function asString(value) {
29
+ if (value == null)
30
+ return '';
31
+ if (typeof value === 'string')
32
+ return value;
33
+ if (typeof value === 'number' || typeof value === 'boolean')
34
+ return String(value);
35
+ return '';
36
+ }
37
+ function coerceBoolean(value) {
38
+ if (typeof value === 'boolean')
39
+ return value;
40
+ if (typeof value === 'number')
41
+ return value === 1;
42
+ return TRUTHY.has(asString(value).trim().toLowerCase());
43
+ }
44
+ function evalRule(rule, values) {
45
+ const current = asString(values[rule.fieldKey]).toLowerCase().trim();
46
+ const target = asString(rule.value).toLowerCase().trim();
47
+ switch (rule.operator) {
48
+ case 'equals':
49
+ return current === target;
50
+ case 'notEquals':
51
+ return current !== target;
52
+ case 'contains':
53
+ return current.includes(target);
54
+ case 'isEmpty':
55
+ return current === '';
56
+ case 'isNotEmpty':
57
+ return current !== '';
58
+ default:
59
+ return true;
60
+ }
61
+ }
62
+ /** A field is visible unless its `show` rules exist and not all of them pass. */
63
+ function isVisible(field, values) {
64
+ const show = field.conditionalLogic?.show;
65
+ if (!show || show.length === 0)
66
+ return true;
67
+ return show.every((rule) => evalRule(rule, values));
68
+ }
69
+ /** A field is conditionally required when its `require` rules all pass. */
70
+ function isConditionallyRequired(field, values) {
71
+ const require = field.conditionalLogic?.require;
72
+ if (!require || require.length === 0)
73
+ return false;
74
+ return require.every((rule) => evalRule(rule, values));
75
+ }
76
+ function isPresent(raw) {
77
+ if (raw === undefined || raw === null)
78
+ return false;
79
+ if (typeof raw === 'string')
80
+ return raw.trim() !== '';
81
+ if (Array.isArray(raw))
82
+ return raw.length > 0;
83
+ return true;
84
+ }
85
+ export function validateAndNormalizeSubmission(fields, rawValues, options = {}) {
86
+ const errors = [];
87
+ const data = {};
88
+ const normalized = {};
89
+ const fileKeys = options.fileKeys ?? new Set();
90
+ for (const field of fields) {
91
+ if (NON_INPUT.has(field.type))
92
+ continue;
93
+ if (!isVisible(field, rawValues))
94
+ continue;
95
+ const key = field.key;
96
+ const label = field.label || key;
97
+ const rules = field.validationRules ?? {};
98
+ const required = Boolean(field.required) ||
99
+ Boolean(rules.required) ||
100
+ isConditionallyRequired(field, rawValues);
101
+ if (field.type === 'file') {
102
+ if (required && !fileKeys.has(key)) {
103
+ errors.push({ key, message: `${label} is required.` });
104
+ }
105
+ continue;
106
+ }
107
+ const raw = rawValues[key];
108
+ if (!isPresent(raw)) {
109
+ if (required)
110
+ errors.push({ key, message: `${label} is required.` });
111
+ continue;
112
+ }
113
+ switch (field.type) {
114
+ case 'checkbox':
115
+ case 'consent': {
116
+ const bool = coerceBoolean(raw);
117
+ if ((field.type === 'consent' || required) && !bool) {
118
+ errors.push({ key, message: `${label} must be accepted.` });
119
+ }
120
+ data[key] = bool;
121
+ normalized[key] = bool;
122
+ break;
123
+ }
124
+ case 'number': {
125
+ const num = typeof raw === 'number' ? raw : Number(asString(raw).trim());
126
+ if (!Number.isFinite(num)) {
127
+ errors.push({ key, message: `${label} must be a number.` });
128
+ break;
129
+ }
130
+ if (rules.min != null && num < rules.min) {
131
+ errors.push({ key, message: `${label} must be at least ${rules.min}.` });
132
+ }
133
+ if (rules.max != null && num > rules.max) {
134
+ errors.push({ key, message: `${label} must be at most ${rules.max}.` });
135
+ }
136
+ data[key] = num;
137
+ normalized[key] = num;
138
+ break;
139
+ }
140
+ case 'multiselect': {
141
+ const list = Array.isArray(raw) ? raw : [raw];
142
+ if (list.length > MAX_MULTISELECT_ITEMS) {
143
+ errors.push({ key, message: `${label} has too many selections.` });
144
+ break;
145
+ }
146
+ const cleaned = list.map((v) => stripHtml(asString(v)).trim()).filter((v) => v !== '');
147
+ const allowed = new Set((field.options ?? []).map((o) => o.value));
148
+ if (allowed.size > 0 && cleaned.some((v) => !allowed.has(v))) {
149
+ errors.push({ key, message: `${label} contains an invalid selection.` });
150
+ }
151
+ data[key] = cleaned;
152
+ normalized[key] = cleaned;
153
+ break;
154
+ }
155
+ case 'select':
156
+ case 'radio': {
157
+ const val = stripHtml(asString(raw)).trim();
158
+ const allowed = new Set((field.options ?? []).map((o) => o.value));
159
+ if (allowed.size > 0 && !allowed.has(val)) {
160
+ errors.push({ key, message: `${label} contains an invalid selection.` });
161
+ }
162
+ data[key] = val;
163
+ normalized[key] = val;
164
+ break;
165
+ }
166
+ case 'date': {
167
+ const s = asString(raw).trim();
168
+ const d = new Date(s);
169
+ if (Number.isNaN(d.getTime())) {
170
+ errors.push({ key, message: `${label} is not a valid date.` });
171
+ data[key] = s;
172
+ }
173
+ else {
174
+ data[key] = s;
175
+ normalized[key] = d.toISOString();
176
+ }
177
+ break;
178
+ }
179
+ default: {
180
+ // text, email, phone, url, textarea, hidden
181
+ const s = stripHtml(asString(raw)).trim();
182
+ const maxLen = rules.maxLength ?? (field.type === 'textarea' ? MAX_TEXTAREA_LEN : MAX_TEXT_LEN);
183
+ if (s.length > maxLen) {
184
+ errors.push({ key, message: `${label} must be at most ${maxLen} characters.` });
185
+ }
186
+ if (rules.minLength != null && s.length < rules.minLength) {
187
+ errors.push({ key, message: `${label} must be at least ${rules.minLength} characters.` });
188
+ }
189
+ const format = rules.format ??
190
+ (field.type === 'email'
191
+ ? 'email'
192
+ : field.type === 'url'
193
+ ? 'url'
194
+ : field.type === 'phone'
195
+ ? 'phone'
196
+ : undefined);
197
+ if (format === 'email' && !EMAIL_RE.test(s)) {
198
+ errors.push({ key, message: `${label} must be a valid email address.` });
199
+ }
200
+ else if (format === 'phone' && !PHONE_RE.test(s)) {
201
+ errors.push({ key, message: `${label} must be a valid phone number.` });
202
+ }
203
+ else if (format === 'url') {
204
+ if (!isHttpUrl(s)) {
205
+ errors.push({ key, message: `${label} must be a valid URL.` });
206
+ }
207
+ }
208
+ if (rules.pattern) {
209
+ try {
210
+ if (!new RegExp(rules.pattern).test(s)) {
211
+ errors.push({ key, message: `${label} is not in the expected format.` });
212
+ }
213
+ }
214
+ catch {
215
+ // An invalid pattern in the schema must never block a submission;
216
+ // schema validation already surfaces bad patterns to the editor.
217
+ }
218
+ }
219
+ data[key] = s;
220
+ normalized[key] = s;
221
+ }
222
+ }
223
+ }
224
+ return { ok: errors.length === 0, errors, data, normalized };
225
+ }
226
+ function isHttpUrl(value) {
227
+ try {
228
+ const url = new URL(value);
229
+ return url.protocol === 'http:' || url.protocol === 'https:';
230
+ }
231
+ catch {
232
+ return false;
233
+ }
234
+ }
235
+ //# sourceMappingURL=validation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.js","sourceRoot":"","sources":["../../src/forms/validation.ts"],"names":[],"mappings":"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"}