@saastro/forms 0.1.3

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.
@@ -0,0 +1,687 @@
1
+ // src/utils/submitActionExecutors.ts
2
+ function replacePlaceholders(template, values) {
3
+ return template.replace(/\{\{(\w+)\}\}/g, (_, fieldName) => {
4
+ const value = values[fieldName];
5
+ if (value === void 0 || value === null) return "";
6
+ if (typeof value === "object") return JSON.stringify(value);
7
+ return String(value);
8
+ });
9
+ }
10
+ function filterFields(values, includeFields, excludeFields) {
11
+ let result = { ...values };
12
+ if (includeFields && includeFields.length > 0) {
13
+ result = {};
14
+ for (const field of includeFields) {
15
+ if (field in values) {
16
+ result[field] = values[field];
17
+ }
18
+ }
19
+ }
20
+ if (excludeFields && excludeFields.length > 0) {
21
+ for (const field of excludeFields) {
22
+ delete result[field];
23
+ }
24
+ }
25
+ return result;
26
+ }
27
+ function buildAuthHeaders(auth) {
28
+ if (!auth || auth.type === "none") return {};
29
+ switch (auth.type) {
30
+ case "bearer":
31
+ return auth.token ? { Authorization: `Bearer ${auth.token}` } : {};
32
+ case "basic":
33
+ if (auth.username && auth.password) {
34
+ const credentials = btoa(`${auth.username}:${auth.password}`);
35
+ return { Authorization: `Basic ${credentials}` };
36
+ }
37
+ return {};
38
+ case "api-key":
39
+ if (auth.token) {
40
+ const headerName = auth.headerName || "X-API-Key";
41
+ return { [headerName]: auth.token };
42
+ }
43
+ return {};
44
+ default:
45
+ return {};
46
+ }
47
+ }
48
+ function buildRequestBody(values, bodyConfig) {
49
+ const filteredValues = filterFields(values, bodyConfig?.includeFields, bodyConfig?.excludeFields);
50
+ const format = bodyConfig?.format || "json";
51
+ switch (format) {
52
+ case "json": {
53
+ const data = bodyConfig?.template ? JSON.parse(replacePlaceholders(bodyConfig.template, values)) : filteredValues;
54
+ return {
55
+ body: JSON.stringify(data),
56
+ contentType: "application/json"
57
+ };
58
+ }
59
+ case "form-data": {
60
+ const formData = new FormData();
61
+ for (const [key, value] of Object.entries(filteredValues)) {
62
+ if (value instanceof File) {
63
+ formData.append(key, value);
64
+ } else if (value !== void 0 && value !== null) {
65
+ formData.append(key, String(value));
66
+ }
67
+ }
68
+ return {
69
+ body: formData,
70
+ contentType: ""
71
+ // Browser sets this automatically with boundary
72
+ };
73
+ }
74
+ case "url-encoded": {
75
+ const params = new URLSearchParams();
76
+ for (const [key, value] of Object.entries(filteredValues)) {
77
+ if (value !== void 0 && value !== null) {
78
+ params.append(key, String(value));
79
+ }
80
+ }
81
+ return {
82
+ body: params.toString(),
83
+ contentType: "application/x-www-form-urlencoded"
84
+ };
85
+ }
86
+ default:
87
+ return {
88
+ body: JSON.stringify(filteredValues),
89
+ contentType: "application/json"
90
+ };
91
+ }
92
+ }
93
+ async function executeWithRetry(fn, retryConfig) {
94
+ const maxAttempts = retryConfig?.enabled ? retryConfig.maxAttempts : 1;
95
+ const delayMs = retryConfig?.delayMs || 1e3;
96
+ let lastError;
97
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
98
+ try {
99
+ return await fn();
100
+ } catch (error) {
101
+ lastError = error instanceof Error ? error : new Error(String(error));
102
+ if (attempt < maxAttempts) {
103
+ await new Promise((resolve) => setTimeout(resolve, delayMs * attempt));
104
+ }
105
+ }
106
+ }
107
+ throw lastError;
108
+ }
109
+ async function executeHttpAction(action, values) {
110
+ const { endpoint, body: bodyConfig, auth, retry, timeout = 3e4 } = action;
111
+ const execute = async () => {
112
+ const controller = new AbortController();
113
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
114
+ try {
115
+ const authHeaders = buildAuthHeaders(auth);
116
+ const { body, contentType } = buildRequestBody(values, bodyConfig);
117
+ const headers = {
118
+ ...endpoint.headers,
119
+ ...authHeaders
120
+ };
121
+ if (contentType) {
122
+ headers["Content-Type"] = contentType;
123
+ }
124
+ const response = await fetch(endpoint.url, {
125
+ method: endpoint.method,
126
+ headers,
127
+ body: endpoint.method !== "GET" ? body : void 0,
128
+ signal: controller.signal
129
+ });
130
+ if (!response.ok) {
131
+ const errorText = await response.text().catch(() => "Unknown error");
132
+ throw new Error(`HTTP ${response.status}: ${response.statusText} - ${errorText}`);
133
+ }
134
+ const responseText = await response.text();
135
+ try {
136
+ return JSON.parse(responseText);
137
+ } catch {
138
+ return responseText;
139
+ }
140
+ } finally {
141
+ clearTimeout(timeoutId);
142
+ }
143
+ };
144
+ return executeWithRetry(execute, retry);
145
+ }
146
+ async function generateHmacSignature(payload, secret) {
147
+ const encoder = new TextEncoder();
148
+ const keyData = encoder.encode(secret);
149
+ const messageData = encoder.encode(payload);
150
+ const key = await crypto.subtle.importKey(
151
+ "raw",
152
+ keyData,
153
+ { name: "HMAC", hash: "SHA-256" },
154
+ false,
155
+ ["sign"]
156
+ );
157
+ const signature = await crypto.subtle.sign("HMAC", key, messageData);
158
+ const hashArray = Array.from(new Uint8Array(signature));
159
+ return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
160
+ }
161
+ async function executeWebhookAction(action, values) {
162
+ const { url, secret, headers = {}, payloadTemplate, includeFields, excludeFields } = action;
163
+ const filteredValues = filterFields(values, includeFields, excludeFields);
164
+ const payload = payloadTemplate ? replacePlaceholders(payloadTemplate, values) : JSON.stringify(filteredValues);
165
+ const requestHeaders = {
166
+ "Content-Type": "application/json",
167
+ ...headers
168
+ };
169
+ if (secret) {
170
+ const signature = await generateHmacSignature(payload, secret);
171
+ requestHeaders["X-Webhook-Signature"] = `sha256=${signature}`;
172
+ }
173
+ const response = await fetch(url, {
174
+ method: "POST",
175
+ headers: requestHeaders,
176
+ body: payload
177
+ });
178
+ if (!response.ok) {
179
+ throw new Error(`Webhook failed: ${response.status} ${response.statusText}`);
180
+ }
181
+ const responseText = await response.text();
182
+ try {
183
+ return JSON.parse(responseText);
184
+ } catch {
185
+ return responseText;
186
+ }
187
+ }
188
+ function generateDefaultEmailTemplate(values) {
189
+ const rows = Object.entries(values).map(
190
+ ([key, value]) => `
191
+ <tr>
192
+ <td style="padding: 8px; border: 1px solid #ddd; font-weight: bold;">${key}</td>
193
+ <td style="padding: 8px; border: 1px solid #ddd;">${typeof value === "object" ? JSON.stringify(value) : String(value ?? "")}</td>
194
+ </tr>
195
+ `
196
+ ).join("");
197
+ return `
198
+ <!DOCTYPE html>
199
+ <html>
200
+ <head>
201
+ <meta charset="utf-8">
202
+ <title>Form Submission</title>
203
+ </head>
204
+ <body style="font-family: Arial, sans-serif; padding: 20px;">
205
+ <h2 style="color: #333;">New Form Submission</h2>
206
+ <table style="border-collapse: collapse; width: 100%; max-width: 600px;">
207
+ <thead>
208
+ <tr style="background-color: #f5f5f5;">
209
+ <th style="padding: 8px; border: 1px solid #ddd; text-align: left;">Field</th>
210
+ <th style="padding: 8px; border: 1px solid #ddd; text-align: left;">Value</th>
211
+ </tr>
212
+ </thead>
213
+ <tbody>
214
+ ${rows}
215
+ </tbody>
216
+ </table>
217
+ <p style="color: #666; font-size: 12px; margin-top: 20px;">
218
+ Sent via @saastro/forms
219
+ </p>
220
+ </body>
221
+ </html>
222
+ `;
223
+ }
224
+ async function executeEmailAction(action, values) {
225
+ const {
226
+ provider,
227
+ endpoint,
228
+ apiKey,
229
+ to,
230
+ cc,
231
+ bcc,
232
+ from,
233
+ subject,
234
+ template,
235
+ customTemplate,
236
+ replyTo
237
+ } = action;
238
+ const recipients = Array.isArray(to) ? to : [to];
239
+ const emailSubject = replacePlaceholders(subject, values);
240
+ const emailBody = template === "custom" && customTemplate ? replacePlaceholders(customTemplate, values) : generateDefaultEmailTemplate(values);
241
+ const payload = {
242
+ to: recipients,
243
+ subject: emailSubject,
244
+ html: emailBody
245
+ };
246
+ if (cc) payload.cc = cc;
247
+ if (bcc) payload.bcc = bcc;
248
+ if (from) payload.from = from;
249
+ if (replyTo) payload.replyTo = replyTo;
250
+ let url;
251
+ const headers = {
252
+ "Content-Type": "application/json"
253
+ };
254
+ switch (provider) {
255
+ case "sendgrid":
256
+ url = endpoint || "https://api.sendgrid.com/v3/mail/send";
257
+ if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`;
258
+ payload.personalizations = [{ to: recipients.map((email) => ({ email })) }];
259
+ payload.content = [{ type: "text/html", value: emailBody }];
260
+ delete payload.to;
261
+ delete payload.html;
262
+ break;
263
+ case "resend":
264
+ url = endpoint || "https://api.resend.com/emails";
265
+ if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`;
266
+ break;
267
+ case "mailgun":
268
+ url = endpoint || "https://api.mailgun.net/v3/YOUR_DOMAIN/messages";
269
+ if (apiKey) {
270
+ headers["Authorization"] = `Basic ${btoa(`api:${apiKey}`)}`;
271
+ }
272
+ break;
273
+ case "smtp":
274
+ default:
275
+ url = endpoint || "/api/send-email";
276
+ break;
277
+ }
278
+ const response = await fetch(url, {
279
+ method: "POST",
280
+ headers,
281
+ body: JSON.stringify(payload)
282
+ });
283
+ if (!response.ok) {
284
+ const errorText = await response.text().catch(() => "Unknown error");
285
+ throw new Error(`Email failed: ${response.status} - ${errorText}`);
286
+ }
287
+ return response.json().catch(() => ({ success: true }));
288
+ }
289
+ async function executeCustomAction(action, values) {
290
+ return action.handler(values);
291
+ }
292
+ async function executeSubmitAction(action, values) {
293
+ switch (action.type) {
294
+ case "http":
295
+ return executeHttpAction(action, values);
296
+ case "webhook":
297
+ return executeWebhookAction(action, values);
298
+ case "email":
299
+ return executeEmailAction(action, values);
300
+ case "custom":
301
+ return executeCustomAction(action, values);
302
+ default:
303
+ throw new Error(`Unknown action type: ${action.type}`);
304
+ }
305
+ }
306
+
307
+ // src/utils/fieldMapping.ts
308
+ function isAdvancedMapping(mapping) {
309
+ if (typeof mapping !== "object" || mapping === null) return false;
310
+ return "fields" in mapping || "inject" in mapping || "exclude" in mapping || "passthrough" in mapping;
311
+ }
312
+ function isFieldMapEntry(entry) {
313
+ return typeof entry === "object" && "to" in entry;
314
+ }
315
+ function isResolver(value) {
316
+ return typeof value === "object" && value !== null && "$resolver" in value;
317
+ }
318
+ function formatDateYMD(d) {
319
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
320
+ }
321
+ function formatDateDMY(d) {
322
+ return `${String(d.getDate()).padStart(2, "0")}/${String(d.getMonth() + 1).padStart(2, "0")}/${d.getFullYear()}`;
323
+ }
324
+ function toDate(value) {
325
+ if (value instanceof Date) return value;
326
+ return new Date(String(value));
327
+ }
328
+ function applyBuiltinTransform(value, transform) {
329
+ switch (transform) {
330
+ case "toString":
331
+ return value == null ? "" : String(value);
332
+ case "toNumber":
333
+ return Number(value);
334
+ case "toBoolean":
335
+ return Boolean(value);
336
+ case "booleanString":
337
+ return value ? "true" : "false";
338
+ case "dateISO":
339
+ return toDate(value).toISOString();
340
+ case "dateYMD":
341
+ return formatDateYMD(toDate(value));
342
+ case "dateDMY":
343
+ return formatDateDMY(toDate(value));
344
+ case "dateTimestamp":
345
+ return toDate(value).getTime();
346
+ case "trim":
347
+ return typeof value === "string" ? value.trim() : value;
348
+ case "lowercase":
349
+ return typeof value === "string" ? value.toLowerCase() : value;
350
+ case "uppercase":
351
+ return typeof value === "string" ? value.toUpperCase() : value;
352
+ case "emptyToNull":
353
+ return value === "" || value === void 0 ? null : value;
354
+ default:
355
+ return value;
356
+ }
357
+ }
358
+ function applyTransform(value, transform) {
359
+ if (typeof transform === "function") {
360
+ return transform(value);
361
+ }
362
+ return applyBuiltinTransform(value, transform);
363
+ }
364
+ async function resolveValue(resolver) {
365
+ switch (resolver.$resolver) {
366
+ case "timestamp":
367
+ return (/* @__PURE__ */ new Date()).toISOString();
368
+ case "hostname":
369
+ return typeof window !== "undefined" ? window.location.hostname : "";
370
+ case "pageUrl":
371
+ return typeof window !== "undefined" ? window.location.href : "";
372
+ case "referrer":
373
+ return typeof document !== "undefined" ? document.referrer : "";
374
+ case "userAgent":
375
+ return typeof navigator !== "undefined" ? navigator.userAgent : "";
376
+ case "urlParam": {
377
+ if (typeof window === "undefined") return resolver.fallback ?? "";
378
+ const params = new URLSearchParams(window.location.search);
379
+ return params.get(resolver.param) ?? resolver.fallback ?? "";
380
+ }
381
+ case "ip": {
382
+ const endpoint = resolver.endpoint || "https://api.ipify.org?format=json";
383
+ try {
384
+ const res = await fetch(endpoint);
385
+ const data = await res.json();
386
+ return data.ip ?? resolver.fallback ?? "";
387
+ } catch {
388
+ return resolver.fallback ?? "";
389
+ }
390
+ }
391
+ case "custom":
392
+ return resolver.fn();
393
+ default:
394
+ return void 0;
395
+ }
396
+ }
397
+ function resolveValueSync(resolver) {
398
+ switch (resolver.$resolver) {
399
+ case "timestamp":
400
+ return (/* @__PURE__ */ new Date()).toISOString();
401
+ case "hostname":
402
+ return typeof window !== "undefined" ? window.location.hostname : "";
403
+ case "pageUrl":
404
+ return typeof window !== "undefined" ? window.location.href : "";
405
+ case "referrer":
406
+ return typeof document !== "undefined" ? document.referrer : "";
407
+ case "userAgent":
408
+ return typeof navigator !== "undefined" ? navigator.userAgent : "";
409
+ case "urlParam": {
410
+ if (typeof window === "undefined") return resolver.fallback ?? "";
411
+ const params = new URLSearchParams(window.location.search);
412
+ return params.get(resolver.param) ?? resolver.fallback ?? "";
413
+ }
414
+ case "ip":
415
+ return resolver.fallback ?? "[fetching IP...]";
416
+ case "custom":
417
+ return "[custom resolver]";
418
+ default:
419
+ return void 0;
420
+ }
421
+ }
422
+ async function applyFieldMapping(values, mapping) {
423
+ if (!isAdvancedMapping(mapping)) {
424
+ const result2 = {};
425
+ for (const [key, value] of Object.entries(values)) {
426
+ const mappedKey = mapping[key] || key;
427
+ result2[mappedKey] = value;
428
+ }
429
+ return result2;
430
+ }
431
+ const { fields, inject, exclude, passthrough = true } = mapping;
432
+ const excludeSet = new Set(exclude || []);
433
+ const mappedKeys = /* @__PURE__ */ new Set();
434
+ const result = {};
435
+ if (fields) {
436
+ for (const [formKey, target] of Object.entries(fields)) {
437
+ mappedKeys.add(formKey);
438
+ if (excludeSet.has(formKey)) continue;
439
+ const rawValue = values[formKey];
440
+ if (isFieldMapEntry(target)) {
441
+ result[target.to] = target.transform ? applyTransform(rawValue, target.transform) : rawValue;
442
+ } else {
443
+ result[target] = rawValue;
444
+ }
445
+ }
446
+ }
447
+ if (passthrough) {
448
+ for (const [key, value] of Object.entries(values)) {
449
+ if (!mappedKeys.has(key) && !excludeSet.has(key)) {
450
+ result[key] = value;
451
+ }
452
+ }
453
+ }
454
+ if (inject) {
455
+ const entries = Object.entries(inject);
456
+ const resolverEntries = entries.filter(([, v]) => isResolver(v));
457
+ const staticEntries = entries.filter(([, v]) => !isResolver(v));
458
+ for (const [key, value] of staticEntries) {
459
+ result[key] = value;
460
+ }
461
+ if (resolverEntries.length > 0) {
462
+ const resolved = await Promise.all(
463
+ resolverEntries.map(async ([key, value]) => ({
464
+ key,
465
+ value: await resolveValue(value)
466
+ }))
467
+ );
468
+ for (const { key, value } of resolved) {
469
+ result[key] = value;
470
+ }
471
+ }
472
+ }
473
+ return result;
474
+ }
475
+ function applyFieldMappingSync(values, mapping) {
476
+ if (!isAdvancedMapping(mapping)) {
477
+ const result2 = {};
478
+ for (const [key, value] of Object.entries(values)) {
479
+ const mappedKey = mapping[key] || key;
480
+ result2[mappedKey] = value;
481
+ }
482
+ return result2;
483
+ }
484
+ const { fields, inject, exclude, passthrough = true } = mapping;
485
+ const excludeSet = new Set(exclude || []);
486
+ const mappedKeys = /* @__PURE__ */ new Set();
487
+ const result = {};
488
+ if (fields) {
489
+ for (const [formKey, target] of Object.entries(fields)) {
490
+ mappedKeys.add(formKey);
491
+ if (excludeSet.has(formKey)) continue;
492
+ const rawValue = values[formKey];
493
+ if (isFieldMapEntry(target)) {
494
+ result[target.to] = target.transform ? applyTransform(rawValue, target.transform) : rawValue;
495
+ } else {
496
+ result[target] = rawValue;
497
+ }
498
+ }
499
+ }
500
+ if (passthrough) {
501
+ for (const [key, value] of Object.entries(values)) {
502
+ if (!mappedKeys.has(key) && !excludeSet.has(key)) {
503
+ result[key] = value;
504
+ }
505
+ }
506
+ }
507
+ if (inject) {
508
+ for (const [key, value] of Object.entries(inject)) {
509
+ if (isResolver(value)) {
510
+ result[key] = resolveValueSync(value);
511
+ } else {
512
+ result[key] = value;
513
+ }
514
+ }
515
+ }
516
+ return result;
517
+ }
518
+ function applyFieldTransforms(fields, values) {
519
+ const result = { ...values };
520
+ for (const [fieldName, fieldConfig] of Object.entries(fields)) {
521
+ const transform = fieldConfig?.transform;
522
+ if (!transform || !(fieldName in result)) continue;
523
+ if (typeof transform === "function") {
524
+ result[fieldName] = transform(result[fieldName]);
525
+ } else if (Array.isArray(transform)) {
526
+ result[fieldName] = transform.reduce(
527
+ (v, t) => applyBuiltinTransform(v, t),
528
+ result[fieldName]
529
+ );
530
+ } else {
531
+ result[fieldName] = applyBuiltinTransform(result[fieldName], transform);
532
+ }
533
+ }
534
+ return result;
535
+ }
536
+
537
+ // src/utils/submitOrchestrator.ts
538
+ function evaluateCondition(condition, values) {
539
+ const fieldValue = values[condition.field];
540
+ const compareValue = condition.value;
541
+ const operators = {
542
+ equals: (a, b) => a === b,
543
+ notEquals: (a, b) => a !== b,
544
+ contains: (a, b) => typeof a === "string" && typeof b === "string" && a.includes(b),
545
+ notContains: (a, b) => typeof a === "string" && typeof b === "string" && !a.includes(b),
546
+ greaterThan: (a, b) => Number(a) > Number(b),
547
+ lessThan: (a, b) => Number(a) < Number(b),
548
+ greaterThanOrEqual: (a, b) => Number(a) >= Number(b),
549
+ lessThanOrEqual: (a, b) => Number(a) <= Number(b),
550
+ isTrue: (a) => a === true || a === "true" || a === 1,
551
+ isFalse: (a) => a === false || a === "false" || a === 0,
552
+ isEmpty: (a) => a === void 0 || a === null || a === "" || Array.isArray(a) && a.length === 0,
553
+ isNotEmpty: (a) => a !== void 0 && a !== null && a !== "" && !(Array.isArray(a) && a.length === 0)
554
+ };
555
+ const evaluator = operators[condition.operator];
556
+ return evaluator ? evaluator(fieldValue, compareValue) : true;
557
+ }
558
+ function getActionsByTrigger(config, triggerType, triggerValue) {
559
+ const actions = config.submitActions;
560
+ if (!actions) return [];
561
+ return Object.values(actions).filter((node) => {
562
+ if (node.disabled) return false;
563
+ if (node.trigger.type !== triggerType) return false;
564
+ switch (triggerType) {
565
+ case "onStepEnter":
566
+ case "onStepExit":
567
+ return !node.trigger.stepId || node.trigger.stepId === triggerValue;
568
+ case "onFieldChange":
569
+ case "onFieldBlur":
570
+ return !node.trigger.fieldName || node.trigger.fieldName === triggerValue;
571
+ default:
572
+ return true;
573
+ }
574
+ });
575
+ }
576
+ function getMinDelayMs(config) {
577
+ const actions = config.submitActions;
578
+ if (!actions) return 0;
579
+ const delayActions = Object.values(actions).filter(
580
+ (node) => !node.disabled && node.trigger.type === "onDelay"
581
+ );
582
+ if (delayActions.length === 0) return 0;
583
+ return Math.min(...delayActions.map((node) => node.trigger.delayMs || 3e4));
584
+ }
585
+ async function executeActionNode(node, values) {
586
+ const startedAt = /* @__PURE__ */ new Date();
587
+ if (node.condition && !evaluateCondition(node.condition, values)) {
588
+ return {
589
+ actionId: node.id,
590
+ actionName: node.action.name,
591
+ success: true,
592
+ data: { skipped: true, reason: "Condition not met" },
593
+ durationMs: 0,
594
+ startedAt,
595
+ completedAt: /* @__PURE__ */ new Date()
596
+ };
597
+ }
598
+ const mappedValues = node.fieldMapping ? await applyFieldMapping(values, node.fieldMapping) : values;
599
+ try {
600
+ const data = await executeSubmitAction(node.action, mappedValues);
601
+ const completedAt = /* @__PURE__ */ new Date();
602
+ return {
603
+ actionId: node.id,
604
+ actionName: node.action.name,
605
+ success: true,
606
+ data,
607
+ durationMs: completedAt.getTime() - startedAt.getTime(),
608
+ startedAt,
609
+ completedAt
610
+ };
611
+ } catch (err) {
612
+ const completedAt = /* @__PURE__ */ new Date();
613
+ const error = err instanceof Error ? err : new Error(String(err));
614
+ return {
615
+ actionId: node.id,
616
+ actionName: node.action.name,
617
+ success: false,
618
+ error,
619
+ durationMs: completedAt.getTime() - startedAt.getTime(),
620
+ startedAt,
621
+ completedAt
622
+ };
623
+ }
624
+ }
625
+ async function executeSubmitActions(actions, values, config) {
626
+ const startTime = Date.now();
627
+ const mode = config.submitExecution?.mode || "sequential";
628
+ const stopOnFirstError = config.submitExecution?.stopOnFirstError ?? false;
629
+ const sortedActions = [...actions].sort((a, b) => (a.order || 0) - (b.order || 0));
630
+ const results = [];
631
+ if (mode === "parallel") {
632
+ const promises = sortedActions.map((node) => executeActionNode(node, values));
633
+ const parallelResults = await Promise.all(promises);
634
+ results.push(...parallelResults);
635
+ } else {
636
+ for (const node of sortedActions) {
637
+ const result = await executeActionNode(node, values);
638
+ results.push(result);
639
+ if (!result.success && stopOnFirstError && !node.continueOnError) {
640
+ break;
641
+ }
642
+ }
643
+ }
644
+ const successCount = results.filter((r) => r.success).length;
645
+ const failureCount = results.filter((r) => !r.success).length;
646
+ return {
647
+ results,
648
+ allSuccessful: failureCount === 0,
649
+ successCount,
650
+ failureCount,
651
+ totalDurationMs: Date.now() - startTime
652
+ };
653
+ }
654
+ async function executeSubmitActionsByTrigger(config, triggerType, values, triggerValue) {
655
+ const actions = getActionsByTrigger(config, triggerType, triggerValue);
656
+ if (actions.length === 0) {
657
+ return {
658
+ results: [],
659
+ allSuccessful: true,
660
+ successCount: 0,
661
+ failureCount: 0,
662
+ totalDurationMs: 0
663
+ };
664
+ }
665
+ return executeSubmitActions(actions, values, config);
666
+ }
667
+
668
+ export {
669
+ isAdvancedMapping,
670
+ applyBuiltinTransform,
671
+ applyTransform,
672
+ resolveValue,
673
+ resolveValueSync,
674
+ applyFieldMapping,
675
+ applyFieldMappingSync,
676
+ applyFieldTransforms,
677
+ executeHttpAction,
678
+ executeWebhookAction,
679
+ executeEmailAction,
680
+ executeCustomAction,
681
+ executeSubmitAction,
682
+ getActionsByTrigger,
683
+ getMinDelayMs,
684
+ executeSubmitActions,
685
+ executeSubmitActionsByTrigger
686
+ };
687
+ //# sourceMappingURL=chunk-YXKDN3PU.js.map