@forumone/throughline-forms 0.0.1 → 0.2.1
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.
- package/CHANGELOG.md +30 -0
- package/LICENSE +21 -0
- package/README.md +132 -40
- package/dist/destinations.d.ts +26 -0
- package/dist/destinations.d.ts.map +1 -0
- package/dist/destinations.js +36 -0
- package/dist/destinations.js.map +1 -0
- package/dist/functions/_shared.d.ts +13 -0
- package/dist/functions/_shared.d.ts.map +1 -0
- package/dist/functions/_shared.js +44 -0
- package/dist/functions/_shared.js.map +1 -0
- package/dist/functions/email-destination.d.ts +25 -0
- package/dist/functions/email-destination.d.ts.map +1 -0
- package/dist/functions/email-destination.js +75 -0
- package/dist/functions/email-destination.js.map +1 -0
- package/dist/functions/fan-out.d.ts +24 -0
- package/dist/functions/fan-out.d.ts.map +1 -0
- package/dist/functions/fan-out.js +71 -0
- package/dist/functions/fan-out.js.map +1 -0
- package/dist/functions/index.d.ts +9 -0
- package/dist/functions/index.d.ts.map +1 -0
- package/dist/functions/index.js +5 -0
- package/dist/functions/index.js.map +1 -0
- package/dist/functions/submitter-confirmation.d.ts +25 -0
- package/dist/functions/submitter-confirmation.d.ts.map +1 -0
- package/dist/functions/submitter-confirmation.js +76 -0
- package/dist/functions/submitter-confirmation.js.map +1 -0
- package/dist/functions/webhook-destination.d.ts +20 -0
- package/dist/functions/webhook-destination.d.ts.map +1 -0
- package/dist/functions/webhook-destination.js +74 -0
- package/dist/functions/webhook-destination.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/options.d.ts +74 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +61 -0
- package/dist/options.js.map +1 -0
- package/dist/plugin.d.ts +19 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +180 -0
- package/dist/plugin.js.map +1 -0
- package/dist/policy-fields.d.ts +18 -0
- package/dist/policy-fields.d.ts.map +1 -0
- package/dist/policy-fields.js +115 -0
- package/dist/policy-fields.js.map +1 -0
- package/dist/submit/endpoint.d.ts +16 -0
- package/dist/submit/endpoint.d.ts.map +1 -0
- package/dist/submit/endpoint.js +116 -0
- package/dist/submit/endpoint.js.map +1 -0
- package/dist/submit/ip.d.ts +20 -0
- package/dist/submit/ip.d.ts.map +1 -0
- package/dist/submit/ip.js +48 -0
- package/dist/submit/ip.js.map +1 -0
- package/dist/submit/rate-limit.d.ts +23 -0
- package/dist/submit/rate-limit.d.ts.map +1 -0
- package/dist/submit/rate-limit.js +27 -0
- package/dist/submit/rate-limit.js.map +1 -0
- package/dist/submit/spam.d.ts +10 -0
- package/dist/submit/spam.d.ts.map +1 -0
- package/dist/submit/spam.js +16 -0
- package/dist/submit/spam.js.map +1 -0
- package/dist/templates/FormSubmission.d.ts +21 -0
- package/dist/templates/FormSubmission.d.ts.map +1 -0
- package/dist/templates/FormSubmission.js +32 -0
- package/dist/templates/FormSubmission.js.map +1 -0
- package/dist/templates/SubmitterConfirmation.d.ts +18 -0
- package/dist/templates/SubmitterConfirmation.d.ts.map +1 -0
- package/dist/templates/SubmitterConfirmation.js +14 -0
- package/dist/templates/SubmitterConfirmation.js.map +1 -0
- package/dist/templates/_layout.d.ts +14 -0
- package/dist/templates/_layout.d.ts.map +1 -0
- package/dist/templates/_layout.js +34 -0
- package/dist/templates/_layout.js.map +1 -0
- package/dist/templates/index.d.ts +7 -0
- package/dist/templates/index.d.ts.map +1 -0
- package/dist/templates/index.js +4 -0
- package/dist/templates/index.js.map +1 -0
- package/dist/tools/_field-schema.d.ts +68 -0
- package/dist/tools/_field-schema.d.ts.map +1 -0
- package/dist/tools/_field-schema.js +62 -0
- package/dist/tools/_field-schema.js.map +1 -0
- package/dist/tools/access.d.ts +7 -0
- package/dist/tools/access.d.ts.map +1 -0
- package/dist/tools/access.js +16 -0
- package/dist/tools/access.js.map +1 -0
- package/dist/tools/create-form.d.ts +138 -0
- package/dist/tools/create-form.d.ts.map +1 -0
- package/dist/tools/create-form.js +96 -0
- package/dist/tools/create-form.js.map +1 -0
- package/dist/tools/get-form-submissions.d.ts +27 -0
- package/dist/tools/get-form-submissions.d.ts.map +1 -0
- package/dist/tools/get-form-submissions.js +47 -0
- package/dist/tools/get-form-submissions.js.map +1 -0
- package/dist/tools/index.d.ts +14 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +8 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/list-allowed-destinations.d.ts +10 -0
- package/dist/tools/list-allowed-destinations.d.ts.map +1 -0
- package/dist/tools/list-allowed-destinations.js +14 -0
- package/dist/tools/list-allowed-destinations.js.map +1 -0
- package/dist/tools/update-form-destinations.d.ts +45 -0
- package/dist/tools/update-form-destinations.d.ts.map +1 -0
- package/dist/tools/update-form-destinations.js +75 -0
- package/dist/tools/update-form-destinations.js.map +1 -0
- package/dist/tools/update-form-fields.d.ts +101 -0
- package/dist/tools/update-form-fields.d.ts.map +1 -0
- package/dist/tools/update-form-fields.js +65 -0
- package/dist/tools/update-form-fields.js.map +1 -0
- package/dist/tools/validate-form.d.ts +127 -0
- package/dist/tools/validate-form.d.ts.map +1 -0
- package/dist/tools/validate-form.js +50 -0
- package/dist/tools/validate-form.js.map +1 -0
- package/package.json +71 -7
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Inngest, InngestFunction } from 'inngest';
|
|
2
|
+
import type { Payload } from 'payload';
|
|
3
|
+
import type { EmailClient } from '@forumone/throughline-email';
|
|
4
|
+
import type { ResolvedFormsConfig } from '../options.js';
|
|
5
|
+
export interface SubmitterConfirmationDeps {
|
|
6
|
+
inngest: Inngest;
|
|
7
|
+
payload: Payload;
|
|
8
|
+
resolved: ResolvedFormsConfig;
|
|
9
|
+
getEmailClient: () => EmailClient | undefined;
|
|
10
|
+
brandName?: string;
|
|
11
|
+
/** Override the function id. Default: `form-submitter-confirmation`. */
|
|
12
|
+
id?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Subscribes to `form/submitter-confirmation` (dispatched by the fan-out
|
|
16
|
+
* when the form has confirmation enabled). Reads the submission, finds
|
|
17
|
+
* the email-typed field referenced by `submitterConfirmation.emailFieldName`,
|
|
18
|
+
* and sends a SubmitterConfirmationEmail to that address.
|
|
19
|
+
*
|
|
20
|
+
* Skips silently and logs if anything is missing — a misconfigured
|
|
21
|
+
* confirmation should never block the rest of the fan-out, and the
|
|
22
|
+
* configuration error is admin-fixable.
|
|
23
|
+
*/
|
|
24
|
+
export declare function createSubmitterConfirmationFunction(deps: SubmitterConfirmationDeps): InngestFunction.Any;
|
|
25
|
+
//# sourceMappingURL=submitter-confirmation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"submitter-confirmation.d.ts","sourceRoot":"","sources":["../../src/functions/submitter-confirmation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AACvD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAA;AAE9D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAA;AAGxD,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,OAAO,CAAA;IAChB,OAAO,EAAE,OAAO,CAAA;IAChB,QAAQ,EAAE,mBAAmB,CAAA;IAC7B,cAAc,EAAE,MAAM,WAAW,GAAG,SAAS,CAAA;IAC7C,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,wEAAwE;IACxE,EAAE,CAAC,EAAE,MAAM,CAAA;CACZ;AAED;;;;;;;;;GASG;AACH,wBAAgB,mCAAmC,CACjD,IAAI,EAAE,yBAAyB,GAC9B,eAAe,CAAC,GAAG,CAiFrB"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { SubmitterConfirmationEmail } from '../templates/index.js';
|
|
2
|
+
import { findFieldValue, readSubmissionRows } from './_shared.js';
|
|
3
|
+
/**
|
|
4
|
+
* Subscribes to `form/submitter-confirmation` (dispatched by the fan-out
|
|
5
|
+
* when the form has confirmation enabled). Reads the submission, finds
|
|
6
|
+
* the email-typed field referenced by `submitterConfirmation.emailFieldName`,
|
|
7
|
+
* and sends a SubmitterConfirmationEmail to that address.
|
|
8
|
+
*
|
|
9
|
+
* Skips silently and logs if anything is missing — a misconfigured
|
|
10
|
+
* confirmation should never block the rest of the fan-out, and the
|
|
11
|
+
* configuration error is admin-fixable.
|
|
12
|
+
*/
|
|
13
|
+
export function createSubmitterConfirmationFunction(deps) {
|
|
14
|
+
return deps.inngest.createFunction({
|
|
15
|
+
id: deps.id ?? 'form-submitter-confirmation',
|
|
16
|
+
retries: 3,
|
|
17
|
+
triggers: [{ event: 'form/submitter-confirmation' }],
|
|
18
|
+
}, async ({ event, step, logger }) => {
|
|
19
|
+
const data = (event.data ?? {});
|
|
20
|
+
const submissionId = data.submissionId;
|
|
21
|
+
const formId = data.formId;
|
|
22
|
+
if (!submissionId || !formId)
|
|
23
|
+
return { skipped: true };
|
|
24
|
+
const form = (await step.run('load-form', async () => deps.payload.findByID({ collection: deps.resolved.formsCollectionSlug, id: formId })));
|
|
25
|
+
if (!form)
|
|
26
|
+
return { skipped: true, reason: 'form-not-found' };
|
|
27
|
+
const policy = (form['policy'] ?? {});
|
|
28
|
+
const confirmation = (policy['submitterConfirmation'] ?? {});
|
|
29
|
+
if (confirmation.enabled !== true)
|
|
30
|
+
return { skipped: true, reason: 'confirmation-disabled' };
|
|
31
|
+
if (!confirmation.emailFieldName) {
|
|
32
|
+
logger.warn('submitterConfirmation enabled but no emailFieldName set', { formId });
|
|
33
|
+
return { skipped: true, reason: 'no-email-field' };
|
|
34
|
+
}
|
|
35
|
+
const submission = (await step.run('load-submission', async () => deps.payload.findByID({
|
|
36
|
+
collection: deps.resolved.submissionsCollectionSlug,
|
|
37
|
+
id: submissionId,
|
|
38
|
+
})));
|
|
39
|
+
if (!submission)
|
|
40
|
+
return { skipped: true, reason: 'submission-not-found' };
|
|
41
|
+
const rows = readSubmissionRows(submission['submissionData']);
|
|
42
|
+
const recipient = findFieldValue(rows, confirmation.emailFieldName);
|
|
43
|
+
if (!recipient || !recipient.includes('@')) {
|
|
44
|
+
logger.warn('Submitter email missing or invalid; skipping confirmation', {
|
|
45
|
+
formId,
|
|
46
|
+
field: confirmation.emailFieldName,
|
|
47
|
+
});
|
|
48
|
+
return { skipped: true, reason: 'recipient-invalid' };
|
|
49
|
+
}
|
|
50
|
+
const client = deps.getEmailClient();
|
|
51
|
+
if (!client) {
|
|
52
|
+
throw new Error('Email client not available. Register `emailPlugin` before confirmations can send.');
|
|
53
|
+
}
|
|
54
|
+
const subject = confirmation.subject?.trim() || 'Thank you for your submission';
|
|
55
|
+
const bodyText = confirmation.body?.trim() || 'We received your submission and will be in touch.';
|
|
56
|
+
const recipientName = findFieldValue(rows, 'name') ?? findFieldValue(rows, 'first_name') ?? 'there';
|
|
57
|
+
await step.run(`send-${recipient}`, async () => {
|
|
58
|
+
await client.send({
|
|
59
|
+
to: recipient,
|
|
60
|
+
subject,
|
|
61
|
+
template: SubmitterConfirmationEmail({
|
|
62
|
+
recipientName,
|
|
63
|
+
brandName: deps.brandName ?? 'Forms',
|
|
64
|
+
subject,
|
|
65
|
+
body: bodyText,
|
|
66
|
+
}),
|
|
67
|
+
tags: [
|
|
68
|
+
{ name: 'type', value: 'form-submitter-confirmation' },
|
|
69
|
+
{ name: 'form-id', value: formId },
|
|
70
|
+
],
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
return { ok: true, recipient };
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=submitter-confirmation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"submitter-confirmation.js","sourceRoot":"","sources":["../../src/functions/submitter-confirmation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Inngest, InngestFunction } from 'inngest';
|
|
2
|
+
import type { Payload } from 'payload';
|
|
3
|
+
import type { ResolvedFormsConfig } from '../options.js';
|
|
4
|
+
export interface WebhookDestinationDeps {
|
|
5
|
+
inngest: Inngest;
|
|
6
|
+
payload: Payload;
|
|
7
|
+
resolved: ResolvedFormsConfig;
|
|
8
|
+
/** Override the function id. Default: `form-webhook-destination`. */
|
|
9
|
+
id?: string;
|
|
10
|
+
/** Per-call timeout. Default: 10s. */
|
|
11
|
+
timeoutMs?: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Subscribes to `form/destination.webhook`. POSTs the submission to the
|
|
15
|
+
* allowlist URL with an HMAC-SHA256 signature in `x-throughline-signature`.
|
|
16
|
+
* Reuses the IP-hash secret as the HMAC key so the receiver can verify
|
|
17
|
+
* with a single shared secret regardless of which form fired the event.
|
|
18
|
+
*/
|
|
19
|
+
export declare function createWebhookDestinationFunction(deps: WebhookDestinationDeps): InngestFunction.Any;
|
|
20
|
+
//# sourceMappingURL=webhook-destination.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook-destination.d.ts","sourceRoot":"","sources":["../../src/functions/webhook-destination.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AACvD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAA;AAIxD,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,OAAO,CAAA;IAChB,OAAO,EAAE,OAAO,CAAA;IAChB,QAAQ,EAAE,mBAAmB,CAAA;IAC7B,qEAAqE;IACrE,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,sCAAsC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAID;;;;;GAKG;AACH,wBAAgB,gCAAgC,CAC9C,IAAI,EAAE,sBAAsB,GAC3B,eAAe,CAAC,GAAG,CAmErB"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { hashIp } from '../submit/ip.js';
|
|
2
|
+
import { readSubmissionRows } from './_shared.js';
|
|
3
|
+
const DEFAULT_TIMEOUT_MS = 10_000;
|
|
4
|
+
/**
|
|
5
|
+
* Subscribes to `form/destination.webhook`. POSTs the submission to the
|
|
6
|
+
* allowlist URL with an HMAC-SHA256 signature in `x-throughline-signature`.
|
|
7
|
+
* Reuses the IP-hash secret as the HMAC key so the receiver can verify
|
|
8
|
+
* with a single shared secret regardless of which form fired the event.
|
|
9
|
+
*/
|
|
10
|
+
export function createWebhookDestinationFunction(deps) {
|
|
11
|
+
return deps.inngest.createFunction({
|
|
12
|
+
id: deps.id ?? 'form-webhook-destination',
|
|
13
|
+
retries: 5,
|
|
14
|
+
triggers: [{ event: 'form/destination.webhook' }],
|
|
15
|
+
}, async ({ event, step, logger }) => {
|
|
16
|
+
const data = (event.data ?? {});
|
|
17
|
+
if (!data.submissionId || !data.destinationLabel)
|
|
18
|
+
return { skipped: true };
|
|
19
|
+
const dest = deps.resolved.options.allowedDestinations.find((d) => d.label === data.destinationLabel && d.type === 'webhook');
|
|
20
|
+
if (!dest) {
|
|
21
|
+
logger.warn('Webhook destination no longer on allowlist', { label: data.destinationLabel });
|
|
22
|
+
return { skipped: true, reason: 'destination-removed' };
|
|
23
|
+
}
|
|
24
|
+
const submission = (await step.run('load-submission', async () => deps.payload.findByID({
|
|
25
|
+
collection: deps.resolved.submissionsCollectionSlug,
|
|
26
|
+
id: data.submissionId,
|
|
27
|
+
})));
|
|
28
|
+
if (!submission)
|
|
29
|
+
return { skipped: true, reason: 'submission-not-found' };
|
|
30
|
+
const fields = readSubmissionRows(submission['submissionData']);
|
|
31
|
+
const formId = unwrapId(submission['form']);
|
|
32
|
+
const body = JSON.stringify({
|
|
33
|
+
event: 'form/submission',
|
|
34
|
+
timestamp: Date.now(),
|
|
35
|
+
formId,
|
|
36
|
+
submissionId: data.submissionId,
|
|
37
|
+
destinationLabel: dest.label,
|
|
38
|
+
fields,
|
|
39
|
+
});
|
|
40
|
+
const signature = await hashIp(body, deps.resolved.ipHashSecret);
|
|
41
|
+
const timeout = deps.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
42
|
+
const controller = new AbortController();
|
|
43
|
+
const timer = setTimeout(() => controller.abort(), timeout);
|
|
44
|
+
try {
|
|
45
|
+
const response = await fetch(dest.value, {
|
|
46
|
+
method: 'POST',
|
|
47
|
+
headers: {
|
|
48
|
+
'content-type': 'application/json',
|
|
49
|
+
'x-throughline-event': 'form/submission',
|
|
50
|
+
'x-throughline-signature': `sha256=${signature}`,
|
|
51
|
+
'x-throughline-timestamp': String(Date.now()),
|
|
52
|
+
},
|
|
53
|
+
body,
|
|
54
|
+
signal: controller.signal,
|
|
55
|
+
});
|
|
56
|
+
if (!response.ok) {
|
|
57
|
+
throw new Error(`Webhook delivery failed: HTTP ${response.status}`);
|
|
58
|
+
}
|
|
59
|
+
return { ok: true, destinationLabel: dest.label, status: response.status };
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
clearTimeout(timer);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
function unwrapId(value) {
|
|
67
|
+
if (typeof value === 'string')
|
|
68
|
+
return value;
|
|
69
|
+
if (value && typeof value === 'object' && 'id' in value) {
|
|
70
|
+
return String(value.id);
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=webhook-destination.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook-destination.js","sourceRoot":"","sources":["../../src/functions/webhook-destination.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { formsPlugin, getFormsFunctions } from './plugin.js';
|
|
2
|
+
export { DEFAULT_FORMS_SLUG, DEFAULT_FORM_SUBMISSIONS_SLUG, DEFAULT_PRIVACY_NOTICE, DEFAULT_RATE_LIMIT, MIN_IP_HASH_SECRET_LENGTH, validateOptions, } from './options.js';
|
|
3
|
+
export type { AllowedDestination, DestinationType, FormsPluginOptions, ResolvedFormsConfig, } from './options.js';
|
|
4
|
+
export { listDestinations, validateDestinationLabel } from './destinations.js';
|
|
5
|
+
export type { DestinationLookupResult } from './destinations.js';
|
|
6
|
+
export { addFormPolicyFields } from './policy-fields.js';
|
|
7
|
+
export type { PolicyFieldsOptions } from './policy-fields.js';
|
|
8
|
+
export { createCreateFormTool, createUpdateFormFieldsTool, createUpdateFormDestinationsTool, createGetFormSubmissionsTool, createValidateFormTool, createListAllowedDestinationsTool, } from './tools/index.js';
|
|
9
|
+
export { createFormFanOutFunction, createEmailDestinationFunction, createWebhookDestinationFunction, createSubmitterConfirmationFunction, } from './functions/index.js';
|
|
10
|
+
export { FormsLayout, FormSubmissionEmail, SubmitterConfirmationEmail, } from './templates/index.js';
|
|
11
|
+
export type { FormsLayoutProps, FormSubmissionEmailProps, SubmitterConfirmationEmailProps, FormSubmissionField, } from './templates/index.js';
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAE5D,OAAO,EACL,kBAAkB,EAClB,6BAA6B,EAC7B,sBAAsB,EACtB,kBAAkB,EAClB,yBAAyB,EACzB,eAAe,GAChB,MAAM,cAAc,CAAA;AACrB,YAAY,EACV,kBAAkB,EAClB,eAAe,EACf,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,cAAc,CAAA;AAErB,OAAO,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAA;AAC9E,YAAY,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAA;AAEhE,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AACxD,YAAY,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AAE7D,OAAO,EACL,oBAAoB,EACpB,0BAA0B,EAC1B,gCAAgC,EAChC,4BAA4B,EAC5B,sBAAsB,EACtB,iCAAiC,GAClC,MAAM,kBAAkB,CAAA;AAEzB,OAAO,EACL,wBAAwB,EACxB,8BAA8B,EAC9B,gCAAgC,EAChC,mCAAmC,GACpC,MAAM,sBAAsB,CAAA;AAE7B,OAAO,EACL,WAAW,EACX,mBAAmB,EACnB,0BAA0B,GAC3B,MAAM,sBAAsB,CAAA;AAC7B,YAAY,EACV,gBAAgB,EAChB,wBAAwB,EACxB,+BAA+B,EAC/B,mBAAmB,GACpB,MAAM,sBAAsB,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { formsPlugin, getFormsFunctions } from './plugin.js';
|
|
2
|
+
export { DEFAULT_FORMS_SLUG, DEFAULT_FORM_SUBMISSIONS_SLUG, DEFAULT_PRIVACY_NOTICE, DEFAULT_RATE_LIMIT, MIN_IP_HASH_SECRET_LENGTH, validateOptions, } from './options.js';
|
|
3
|
+
export { listDestinations, validateDestinationLabel } from './destinations.js';
|
|
4
|
+
export { addFormPolicyFields } from './policy-fields.js';
|
|
5
|
+
export { createCreateFormTool, createUpdateFormFieldsTool, createUpdateFormDestinationsTool, createGetFormSubmissionsTool, createValidateFormTool, createListAllowedDestinationsTool, } from './tools/index.js';
|
|
6
|
+
export { createFormFanOutFunction, createEmailDestinationFunction, createWebhookDestinationFunction, createSubmitterConfirmationFunction, } from './functions/index.js';
|
|
7
|
+
export { FormsLayout, FormSubmissionEmail, SubmitterConfirmationEmail, } from './templates/index.js';
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAE5D,OAAO,EACL,kBAAkB,EAClB,6BAA6B,EAC7B,sBAAsB,EACtB,kBAAkB,EAClB,yBAAyB,EACzB,eAAe,GAChB,MAAM,cAAc,CAAA;AAQrB,OAAO,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAA;AAG9E,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AAGxD,OAAO,EACL,oBAAoB,EACpB,0BAA0B,EAC1B,gCAAgC,EAChC,4BAA4B,EAC5B,sBAAsB,EACtB,iCAAiC,GAClC,MAAM,kBAAkB,CAAA;AAEzB,OAAO,EACL,wBAAwB,EACxB,8BAA8B,EAC9B,gCAAgC,EAChC,mCAAmC,GACpC,MAAM,sBAAsB,CAAA;AAE7B,OAAO,EACL,WAAW,EACX,mBAAmB,EACnB,0BAA0B,GAC3B,MAAM,sBAAsB,CAAA"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { Inngest } from 'inngest';
|
|
2
|
+
import type { BaseCorePluginOptions } from '@forumone/throughline-plugin-contract';
|
|
3
|
+
export type DestinationType = 'email' | 'webhook';
|
|
4
|
+
export interface AllowedDestination {
|
|
5
|
+
type: DestinationType;
|
|
6
|
+
/** For email: recipient address. For webhook: full HTTPS URL. */
|
|
7
|
+
value: string;
|
|
8
|
+
/** Admin-facing description. Surfaces in the destinations admin UI. */
|
|
9
|
+
description: string;
|
|
10
|
+
/**
|
|
11
|
+
* Label Claude sees when listing destinations and references when
|
|
12
|
+
* creating / updating forms. Must be unique across the allowlist.
|
|
13
|
+
*/
|
|
14
|
+
label: string;
|
|
15
|
+
}
|
|
16
|
+
export interface FormsPluginOptions extends BaseCorePluginOptions {
|
|
17
|
+
/** Inngest client used to fire `form/submission.received` and friends. */
|
|
18
|
+
inngest: Inngest;
|
|
19
|
+
/**
|
|
20
|
+
* Pre-authorized destinations. Forms can only route submissions to one
|
|
21
|
+
* of these. Adding a destination requires editing the plugin config and
|
|
22
|
+
* redeploying — that friction is the security model. See README.
|
|
23
|
+
*/
|
|
24
|
+
allowedDestinations: AllowedDestination[];
|
|
25
|
+
/**
|
|
26
|
+
* Default privacy notice shown above the submit button. Customize for
|
|
27
|
+
* the deployment's jurisdiction (GDPR, CCPA, etc.). Legal review
|
|
28
|
+
* recommended before shipping forms publicly.
|
|
29
|
+
*/
|
|
30
|
+
defaultPrivacyNotice?: string;
|
|
31
|
+
/** Default for the per-form `requiresExplicitConsent` field. Default: `true`. */
|
|
32
|
+
requireConsentByDefault?: boolean;
|
|
33
|
+
/** Max submissions per IP per hour per form. Default: `5`. */
|
|
34
|
+
rateLimit?: number;
|
|
35
|
+
/**
|
|
36
|
+
* Secret used to HMAC-hash submitter IPs. Falls back to
|
|
37
|
+
* `process.env.FORMS_IP_HASH_SECRET`. Must be at least 32 characters so
|
|
38
|
+
* two deployments don't collide on the same IP → hash mapping.
|
|
39
|
+
*/
|
|
40
|
+
ipHashSecret?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Override the Forms collection slug. Default: `'forms'`. Match the
|
|
43
|
+
* default Form Builder plugin uses unless you intentionally renamed it.
|
|
44
|
+
*/
|
|
45
|
+
formsCollectionSlug?: string;
|
|
46
|
+
/**
|
|
47
|
+
* Override the Form Submissions collection slug. Default: `'form-submissions'`.
|
|
48
|
+
*/
|
|
49
|
+
submissionsCollectionSlug?: string;
|
|
50
|
+
/**
|
|
51
|
+
* Route prefix for the public submit endpoint and the MCP endpoint.
|
|
52
|
+
* Payload prepends `/api`, so the defaults land at `/api/forms/submit`
|
|
53
|
+
* and `/api/forms/mcp`.
|
|
54
|
+
*/
|
|
55
|
+
routePrefix?: string;
|
|
56
|
+
}
|
|
57
|
+
export declare const DEFAULT_FORMS_SLUG = "forms";
|
|
58
|
+
export declare const DEFAULT_FORM_SUBMISSIONS_SLUG = "form-submissions";
|
|
59
|
+
export declare const DEFAULT_RATE_LIMIT = 5;
|
|
60
|
+
export declare const MIN_IP_HASH_SECRET_LENGTH = 32;
|
|
61
|
+
export declare const DEFAULT_PRIVACY_NOTICE: string;
|
|
62
|
+
export interface ResolvedFormsConfig {
|
|
63
|
+
options: FormsPluginOptions;
|
|
64
|
+
ipHashSecret: string;
|
|
65
|
+
formsCollectionSlug: string;
|
|
66
|
+
submissionsCollectionSlug: string;
|
|
67
|
+
routePrefix: string;
|
|
68
|
+
rateLimit: number;
|
|
69
|
+
requireConsentByDefault: boolean;
|
|
70
|
+
defaultPrivacyNotice: string;
|
|
71
|
+
destinationLabels: string[];
|
|
72
|
+
}
|
|
73
|
+
export declare function validateOptions(options: FormsPluginOptions): ResolvedFormsConfig;
|
|
74
|
+
//# sourceMappingURL=options.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAA;AAElF,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,SAAS,CAAA;AAEjD,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,eAAe,CAAA;IACrB,iEAAiE;IACjE,KAAK,EAAE,MAAM,CAAA;IACb,uEAAuE;IACvE,WAAW,EAAE,MAAM,CAAA;IACnB;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,kBAAmB,SAAQ,qBAAqB;IAC/D,0EAA0E;IAC1E,OAAO,EAAE,OAAO,CAAA;IAChB;;;;OAIG;IACH,mBAAmB,EAAE,kBAAkB,EAAE,CAAA;IACzC;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAA;IAC7B,iFAAiF;IACjF,uBAAuB,CAAC,EAAE,OAAO,CAAA;IACjC,8DAA8D;IAC9D,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B;;OAEG;IACH,yBAAyB,CAAC,EAAE,MAAM,CAAA;IAClC;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,eAAO,MAAM,kBAAkB,UAAU,CAAA;AACzC,eAAO,MAAM,6BAA6B,qBAAqB,CAAA;AAC/D,eAAO,MAAM,kBAAkB,IAAI,CAAA;AACnC,eAAO,MAAM,yBAAyB,KAAK,CAAA;AAE3C,eAAO,MAAM,sBAAsB,QAEiE,CAAA;AAEpG,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,kBAAkB,CAAA;IAC3B,YAAY,EAAE,MAAM,CAAA;IACpB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,yBAAyB,EAAE,MAAM,CAAA;IACjC,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,uBAAuB,EAAE,OAAO,CAAA;IAChC,oBAAoB,EAAE,MAAM,CAAA;IAC5B,iBAAiB,EAAE,MAAM,EAAE,CAAA;CAC5B;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,kBAAkB,GAAG,mBAAmB,CA+DhF"}
|
package/dist/options.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export const DEFAULT_FORMS_SLUG = 'forms';
|
|
2
|
+
export const DEFAULT_FORM_SUBMISSIONS_SLUG = 'form-submissions';
|
|
3
|
+
export const DEFAULT_RATE_LIMIT = 5;
|
|
4
|
+
export const MIN_IP_HASH_SECRET_LENGTH = 32;
|
|
5
|
+
export const DEFAULT_PRIVACY_NOTICE = 'By submitting this form, the information you provide will be used to respond to your inquiry. ' +
|
|
6
|
+
'We do not sell or share your information with third parties. See our privacy policy for details.';
|
|
7
|
+
export function validateOptions(options) {
|
|
8
|
+
if (!options.inngest) {
|
|
9
|
+
throw new Error('formsPlugin requires an Inngest client (`options.inngest`).');
|
|
10
|
+
}
|
|
11
|
+
if (!Array.isArray(options.allowedDestinations) || options.allowedDestinations.length === 0) {
|
|
12
|
+
throw new Error('formsPlugin requires at least one entry in `options.allowedDestinations`. The allowlist is the security perimeter — Claude can only route submissions to destinations on this list.');
|
|
13
|
+
}
|
|
14
|
+
const labels = new Set();
|
|
15
|
+
for (const dest of options.allowedDestinations) {
|
|
16
|
+
if (!dest.label || typeof dest.label !== 'string') {
|
|
17
|
+
throw new Error(`Allowed destination is missing a label: ${JSON.stringify(dest)}`);
|
|
18
|
+
}
|
|
19
|
+
if (labels.has(dest.label)) {
|
|
20
|
+
throw new Error(`Duplicate destination label "${dest.label}" — labels must be unique.`);
|
|
21
|
+
}
|
|
22
|
+
labels.add(dest.label);
|
|
23
|
+
if (dest.type === 'email') {
|
|
24
|
+
if (!dest.value || !dest.value.includes('@')) {
|
|
25
|
+
throw new Error(`Allowed destination "${dest.label}" is type "email" but value is not a valid address.`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
else if (dest.type === 'webhook') {
|
|
29
|
+
let url;
|
|
30
|
+
try {
|
|
31
|
+
url = new URL(dest.value);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
throw new Error(`Allowed webhook destination "${dest.label}" is not a valid URL.`);
|
|
35
|
+
}
|
|
36
|
+
if (url.protocol !== 'https:') {
|
|
37
|
+
throw new Error(`Allowed webhook destination "${dest.label}" must use https://.`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
throw new Error(`Allowed destination "${dest.label}" has unknown type "${dest.type}".`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const ipHashSecret = options.ipHashSecret ?? process.env['FORMS_IP_HASH_SECRET'];
|
|
45
|
+
if (!ipHashSecret || ipHashSecret.length < MIN_IP_HASH_SECRET_LENGTH) {
|
|
46
|
+
throw new Error(`formsPlugin requires \`options.ipHashSecret\` or \`FORMS_IP_HASH_SECRET\` env var (>=${MIN_IP_HASH_SECRET_LENGTH} characters).`);
|
|
47
|
+
}
|
|
48
|
+
const routePrefix = options.routePrefix ?? '/forms';
|
|
49
|
+
return {
|
|
50
|
+
options,
|
|
51
|
+
ipHashSecret,
|
|
52
|
+
formsCollectionSlug: options.formsCollectionSlug ?? DEFAULT_FORMS_SLUG,
|
|
53
|
+
submissionsCollectionSlug: options.submissionsCollectionSlug ?? DEFAULT_FORM_SUBMISSIONS_SLUG,
|
|
54
|
+
routePrefix,
|
|
55
|
+
rateLimit: options.rateLimit ?? DEFAULT_RATE_LIMIT,
|
|
56
|
+
requireConsentByDefault: options.requireConsentByDefault ?? true,
|
|
57
|
+
defaultPrivacyNotice: options.defaultPrivacyNotice ?? DEFAULT_PRIVACY_NOTICE,
|
|
58
|
+
destinationLabels: options.allowedDestinations.map((d) => d.label),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=options.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.js","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"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"}
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { CorePlugin } from '@forumone/throughline-plugin-contract';
|
|
2
|
+
import type { InngestFunction } from 'inngest';
|
|
3
|
+
import { type FormsPluginOptions } from './options.js';
|
|
4
|
+
/**
|
|
5
|
+
* Forms plugin. Composes the official Form Builder plugin with the
|
|
6
|
+
* Throughline policy layer:
|
|
7
|
+
*
|
|
8
|
+
* 1. Form Builder runs first to create the `forms` and `form-submissions`
|
|
9
|
+
* collections and the field overrides slot.
|
|
10
|
+
* 2. We layer the policy group onto the forms collection's fields and
|
|
11
|
+
* add the access-control + extra fields onto submissions.
|
|
12
|
+
* 3. We add the public submit endpoint and the MCP endpoint.
|
|
13
|
+
* 4. At init we register tools, register Inngest functions via Symbol so
|
|
14
|
+
* the client app's Inngest endpoint can compose them, and require
|
|
15
|
+
* the audit-log + email capabilities.
|
|
16
|
+
*/
|
|
17
|
+
export declare const formsPlugin: CorePlugin<FormsPluginOptions>;
|
|
18
|
+
export declare function getFormsFunctions(payload: unknown): InngestFunction.Any[];
|
|
19
|
+
//# sourceMappingURL=plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAqB,MAAM,uCAAuC,CAAA;AAI1F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAE9C,OAAO,EAAE,KAAK,kBAAkB,EAAmB,MAAM,cAAc,CAAA;AAgCvE;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,WAAW,EAAE,UAAU,CAAC,kBAAkB,CAiKpD,CAAA;AAEH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,eAAe,CAAC,GAAG,EAAE,CAKzE"}
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { getPluginRegistry } from '@forumone/throughline-plugin-contract';
|
|
2
|
+
import { createMcpHandler, createNamedLogger, defaultLogger } from '@forumone/throughline-core';
|
|
3
|
+
import { getEmailClient as readEmailClient } from '@forumone/throughline-email';
|
|
4
|
+
import { formBuilderPlugin } from '@payloadcms/plugin-form-builder';
|
|
5
|
+
import { validateOptions } from './options.js';
|
|
6
|
+
import { addFormPolicyFields } from './policy-fields.js';
|
|
7
|
+
import { validateDestinationLabel } from './destinations.js';
|
|
8
|
+
import { createSubmitEndpoint } from './submit/endpoint.js';
|
|
9
|
+
import { createCreateFormTool, createGetFormSubmissionsTool, createListAllowedDestinationsTool, createUpdateFormDestinationsTool, createUpdateFormFieldsTool, createValidateFormTool, } from './tools/index.js';
|
|
10
|
+
import { createEmailDestinationFunction, createFormFanOutFunction, createSubmitterConfirmationFunction, createWebhookDestinationFunction, } from './functions/index.js';
|
|
11
|
+
const PLUGIN_ID = '@forumone/throughline-forms';
|
|
12
|
+
const PLUGIN_VERSION = '0.1.0';
|
|
13
|
+
const MCP_HANDLER_SYMBOL = Symbol.for('@forumone/throughline/forms-mcp-handler');
|
|
14
|
+
const FUNCTIONS_SYMBOL = Symbol.for('@forumone/throughline/forms-functions');
|
|
15
|
+
/**
|
|
16
|
+
* Forms plugin. Composes the official Form Builder plugin with the
|
|
17
|
+
* Throughline policy layer:
|
|
18
|
+
*
|
|
19
|
+
* 1. Form Builder runs first to create the `forms` and `form-submissions`
|
|
20
|
+
* collections and the field overrides slot.
|
|
21
|
+
* 2. We layer the policy group onto the forms collection's fields and
|
|
22
|
+
* add the access-control + extra fields onto submissions.
|
|
23
|
+
* 3. We add the public submit endpoint and the MCP endpoint.
|
|
24
|
+
* 4. At init we register tools, register Inngest functions via Symbol so
|
|
25
|
+
* the client app's Inngest endpoint can compose them, and require
|
|
26
|
+
* the audit-log + email capabilities.
|
|
27
|
+
*/
|
|
28
|
+
export const formsPlugin = (rawOptions) => (incomingConfig) => {
|
|
29
|
+
if (rawOptions.enabled === false)
|
|
30
|
+
return incomingConfig;
|
|
31
|
+
const resolved = validateOptions(rawOptions);
|
|
32
|
+
const logger = createNamedLogger('forms', rawOptions.logger ?? defaultLogger);
|
|
33
|
+
const policyFieldsOptions = {
|
|
34
|
+
availableDestinationLabels: resolved.destinationLabels,
|
|
35
|
+
defaultPrivacyNotice: resolved.defaultPrivacyNotice,
|
|
36
|
+
requireConsentByDefault: resolved.requireConsentByDefault,
|
|
37
|
+
defaultRateLimit: resolved.rateLimit,
|
|
38
|
+
};
|
|
39
|
+
const formBuilderConfig = formBuilderPlugin({
|
|
40
|
+
fields: {
|
|
41
|
+
text: true,
|
|
42
|
+
textarea: true,
|
|
43
|
+
email: true,
|
|
44
|
+
select: true,
|
|
45
|
+
checkbox: true,
|
|
46
|
+
number: true,
|
|
47
|
+
message: true,
|
|
48
|
+
},
|
|
49
|
+
formOverrides: {
|
|
50
|
+
fields: ({ defaultFields }) => addFormPolicyFields(defaultFields, policyFieldsOptions),
|
|
51
|
+
hooks: {
|
|
52
|
+
beforeChange: [
|
|
53
|
+
async ({ data, operation }) => {
|
|
54
|
+
if (operation !== 'create' && operation !== 'update')
|
|
55
|
+
return data;
|
|
56
|
+
const policy = (data?.['policy'] ?? {});
|
|
57
|
+
const destinations = Array.isArray(policy['destinations'])
|
|
58
|
+
? policy['destinations']
|
|
59
|
+
: [];
|
|
60
|
+
for (const dest of destinations) {
|
|
61
|
+
if (!dest.label)
|
|
62
|
+
continue;
|
|
63
|
+
const check = validateDestinationLabel(resolved.options, dest.label);
|
|
64
|
+
if (!check.ok) {
|
|
65
|
+
throw new Error(`Destination "${dest.label}" is not on the allowlist. Adding it requires editing the plugin config and redeploying.`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return data;
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
formSubmissionOverrides: {
|
|
74
|
+
access: {
|
|
75
|
+
read: ({ req }) => {
|
|
76
|
+
const roles = req.user?.['roles'] ?? [];
|
|
77
|
+
return roles.includes('admin') || roles.includes('form-admin');
|
|
78
|
+
},
|
|
79
|
+
create: () => true,
|
|
80
|
+
update: () => false,
|
|
81
|
+
delete: ({ req }) => {
|
|
82
|
+
const roles = req.user?.['roles'] ?? [];
|
|
83
|
+
return roles.includes('admin');
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
fields: ({ defaultFields }) => [
|
|
87
|
+
...defaultFields,
|
|
88
|
+
{
|
|
89
|
+
name: 'ipHash',
|
|
90
|
+
type: 'text',
|
|
91
|
+
admin: { readOnly: true },
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: 'consentGivenAt',
|
|
95
|
+
type: 'date',
|
|
96
|
+
admin: { readOnly: true },
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
const withFormBuilder = formBuilderConfig(incomingConfig);
|
|
102
|
+
const submitEndpoint = createSubmitEndpoint(resolved);
|
|
103
|
+
const mcpEndpoint = {
|
|
104
|
+
path: `${resolved.routePrefix}/mcp`,
|
|
105
|
+
method: 'post',
|
|
106
|
+
handler: async (req) => {
|
|
107
|
+
const handler = req.payload[MCP_HANDLER_SYMBOL];
|
|
108
|
+
if (!handler) {
|
|
109
|
+
return new Response(JSON.stringify({ error: 'Forms MCP not initialized' }), {
|
|
110
|
+
status: 503,
|
|
111
|
+
headers: { 'content-type': 'application/json' },
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return handler(req);
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
return {
|
|
118
|
+
...withFormBuilder,
|
|
119
|
+
endpoints: [
|
|
120
|
+
...(withFormBuilder.endpoints ?? []),
|
|
121
|
+
submitEndpoint,
|
|
122
|
+
mcpEndpoint,
|
|
123
|
+
],
|
|
124
|
+
onInit: async (payload) => {
|
|
125
|
+
if (withFormBuilder.onInit)
|
|
126
|
+
await withFormBuilder.onInit(payload);
|
|
127
|
+
const registry = getPluginRegistry(payload);
|
|
128
|
+
registry.requireCapability('audit-log', PLUGIN_ID);
|
|
129
|
+
registry.requireCapability('email', PLUGIN_ID);
|
|
130
|
+
const tools = [
|
|
131
|
+
createListAllowedDestinationsTool({ options: resolved.options }),
|
|
132
|
+
createValidateFormTool({ options: resolved.options }),
|
|
133
|
+
createCreateFormTool({ payload, resolved }),
|
|
134
|
+
createUpdateFormFieldsTool({ payload, resolved }),
|
|
135
|
+
createUpdateFormDestinationsTool({ payload, resolved }),
|
|
136
|
+
createGetFormSubmissionsTool({ payload, resolved }),
|
|
137
|
+
];
|
|
138
|
+
const handler = createMcpHandler({
|
|
139
|
+
payload,
|
|
140
|
+
serverName: 'forms',
|
|
141
|
+
tools,
|
|
142
|
+
logger,
|
|
143
|
+
});
|
|
144
|
+
Object.defineProperty(payload, MCP_HANDLER_SYMBOL, {
|
|
145
|
+
value: handler,
|
|
146
|
+
enumerable: false,
|
|
147
|
+
writable: false,
|
|
148
|
+
configurable: false,
|
|
149
|
+
});
|
|
150
|
+
const fanOutDeps = { inngest: resolved.options.inngest, payload, resolved };
|
|
151
|
+
const getEmailClient = () => readEmailClient(payload);
|
|
152
|
+
const functions = [
|
|
153
|
+
createFormFanOutFunction(fanOutDeps),
|
|
154
|
+
createEmailDestinationFunction({ ...fanOutDeps, getEmailClient }),
|
|
155
|
+
createWebhookDestinationFunction(fanOutDeps),
|
|
156
|
+
createSubmitterConfirmationFunction({ ...fanOutDeps, getEmailClient }),
|
|
157
|
+
];
|
|
158
|
+
Object.defineProperty(payload, FUNCTIONS_SYMBOL, {
|
|
159
|
+
value: functions,
|
|
160
|
+
enumerable: false,
|
|
161
|
+
writable: false,
|
|
162
|
+
configurable: false,
|
|
163
|
+
});
|
|
164
|
+
registry.register({
|
|
165
|
+
id: PLUGIN_ID,
|
|
166
|
+
version: PLUGIN_VERSION,
|
|
167
|
+
capabilities: ['forms', 'form-submission-ingest'],
|
|
168
|
+
});
|
|
169
|
+
logger.info('Forms server ready', {
|
|
170
|
+
allowedDestinations: resolved.options.allowedDestinations.length,
|
|
171
|
+
routePrefix: resolved.routePrefix,
|
|
172
|
+
});
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
};
|
|
176
|
+
export function getFormsFunctions(payload) {
|
|
177
|
+
return (payload[FUNCTIONS_SYMBOL] ??
|
|
178
|
+
[]);
|
|
179
|
+
}
|
|
180
|
+
//# sourceMappingURL=plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAA;AACzE,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAA;AAC/F,OAAO,EAAE,cAAc,IAAI,eAAe,EAAE,MAAM,6BAA6B,CAAA;AAE/E,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAA;AACnE,OAAO,EAA2B,eAAe,EAAE,MAAM,cAAc,CAAA;AACvE,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AACxD,OAAO,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAA;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAC3D,OAAO,EACL,oBAAoB,EACpB,4BAA4B,EAC5B,iCAAiC,EACjC,gCAAgC,EAChC,0BAA0B,EAC1B,sBAAsB,GACvB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EACL,8BAA8B,EAC9B,wBAAwB,EACxB,mCAAmC,EACnC,gCAAgC,GACjC,MAAM,sBAAsB,CAAA;AAE7B,MAAM,SAAS,GAAG,6BAA6B,CAAA;AAC/C,MAAM,cAAc,GAAG,OAAO,CAAA;AAE9B,MAAM,kBAAkB,GAAG,MAAM,CAAC,GAAG,CAAC,yCAAyC,CAAC,CAAA;AAChF,MAAM,gBAAgB,GAAG,MAAM,CAAC,GAAG,CAAC,uCAAuC,CAAC,CAAA;AAS5E;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,WAAW,GACtB,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,cAAc,EAAE,EAAE;IACjC,IAAI,UAAU,CAAC,OAAO,KAAK,KAAK;QAAE,OAAO,cAAc,CAAA;IAEvD,MAAM,QAAQ,GAAG,eAAe,CAAC,UAAU,CAAC,CAAA;IAC5C,MAAM,MAAM,GAAG,iBAAiB,CAAC,OAAO,EAAE,UAAU,CAAC,MAAM,IAAI,aAAa,CAAC,CAAA;IAE7E,MAAM,mBAAmB,GAAG;QAC1B,0BAA0B,EAAE,QAAQ,CAAC,iBAAiB;QACtD,oBAAoB,EAAE,QAAQ,CAAC,oBAAoB;QACnD,uBAAuB,EAAE,QAAQ,CAAC,uBAAuB;QACzD,gBAAgB,EAAE,QAAQ,CAAC,SAAS;KACrC,CAAA;IAED,MAAM,iBAAiB,GAAG,iBAAiB,CAAC;QAC1C,MAAM,EAAE;YACN,IAAI,EAAE,IAAI;YACV,QAAQ,EAAE,IAAI;YACd,KAAK,EAAE,IAAI;YACX,MAAM,EAAE,IAAI;YACZ,QAAQ,EAAE,IAAI;YACd,MAAM,EAAE,IAAI;YACZ,OAAO,EAAE,IAAI;SACd;QACD,aAAa,EAAE;YACb,MAAM,EAAE,CAAC,EAAE,aAAa,EAAE,EAAE,EAAE,CAAC,mBAAmB,CAAC,aAAa,EAAE,mBAAmB,CAAC;YACtF,KAAK,EAAE;gBACL,YAAY,EAAE;oBACZ,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE;wBAC5B,IAAI,SAAS,KAAK,QAAQ,IAAI,SAAS,KAAK,QAAQ;4BAAE,OAAO,IAAI,CAAA;wBACjE,MAAM,MAAM,GAAG,CAAE,IAA4C,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,CAA4B,CAAA;wBAC3G,MAAM,YAAY,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;4BACxD,CAAC,CAAE,MAAM,CAAC,cAAc,CAA4B;4BACpD,CAAC,CAAC,EAAE,CAAA;wBACN,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;4BAChC,IAAI,CAAC,IAAI,CAAC,KAAK;gCAAE,SAAQ;4BACzB,MAAM,KAAK,GAAG,wBAAwB,CAAC,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,CAAA;4BACpE,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC;gCACd,MAAM,IAAI,KAAK,CACb,gBAAgB,IAAI,CAAC,KAAK,0FAA0F,CACrH,CAAA;4BACH,CAAC;wBACH,CAAC;wBACD,OAAO,IAAI,CAAA;oBACb,CAAC;iBACF;aACF;SACF;QACD,uBAAuB,EAAE;YACvB,MAAM,EAAE;gBACN,IAAI,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE;oBAChB,MAAM,KAAK,GAAI,GAAG,CAAC,IAAI,EAAE,CAAC,OAAO,CAA0B,IAAI,EAAE,CAAA;oBACjE,OAAO,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAA;gBAChE,CAAC;gBACD,MAAM,EAAE,GAAG,EAAE,CAAC,IAAI;gBAClB,MAAM,EAAE,GAAG,EAAE,CAAC,KAAK;gBACnB,MAAM,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE;oBAClB,MAAM,KAAK,GAAI,GAAG,CAAC,IAAI,EAAE,CAAC,OAAO,CAA0B,IAAI,EAAE,CAAA;oBACjE,OAAO,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAA;gBAChC,CAAC;aACF;YACD,MAAM,EAAE,CAAC,EAAE,aAAa,EAAE,EAAE,EAAE,CAAC;gBAC7B,GAAG,aAAa;gBAChB;oBACE,IAAI,EAAE,QAAQ;oBACd,IAAI,EAAE,MAAM;oBACZ,KAAK,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE;iBAC1B;gBACD;oBACE,IAAI,EAAE,gBAAgB;oBACtB,IAAI,EAAE,MAAM;oBACZ,KAAK,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE;iBAC1B;aACF;SACF;KACF,CAAC,CAAA;IAEF,MAAM,eAAe,GAAG,iBAAiB,CAAC,cAAc,CAAC,CAAA;IACzD,MAAM,cAAc,GAAG,oBAAoB,CAAC,QAAQ,CAAC,CAAA;IACrD,MAAM,WAAW,GAAG;QAClB,IAAI,EAAE,GAAG,QAAQ,CAAC,WAAW,MAAM;QACnC,MAAM,EAAE,MAAe;QACvB,OAAO,EAAE,KAAK,EAAE,GAAwB,EAAE,EAAE;YAC1C,MAAM,OAAO,GAAI,GAAG,CAAC,OAAmC,CAAC,kBAAkB,CAE9D,CAAA;YACb,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,2BAA2B,EAAE,CAAC,EAAE;oBAC1E,MAAM,EAAE,GAAG;oBACX,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;iBAChD,CAAC,CAAA;YACJ,CAAC;YACD,OAAO,OAAO,CAAC,GAAyB,CAAC,CAAA;QAC3C,CAAC;KACF,CAAA;IAED,OAAO;QACL,GAAG,eAAe;QAClB,SAAS,EAAE;YACT,GAAG,CAAC,eAAe,CAAC,SAAS,IAAI,EAAE,CAAC;YACpC,cAAc;YACd,WAAW;SACqC;QAClD,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;YACxB,IAAI,eAAe,CAAC,MAAM;gBAAE,MAAM,eAAe,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;YAEjE,MAAM,QAAQ,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAA;YAC3C,QAAQ,CAAC,iBAAiB,CAAC,WAAW,EAAE,SAAS,CAAC,CAAA;YAClD,QAAQ,CAAC,iBAAiB,CAAC,OAAO,EAAE,SAAS,CAAC,CAAA;YAE9C,MAAM,KAAK,GAAG;gBACZ,iCAAiC,CAAC,EAAE,OAAO,EAAE,QAAQ,CAAC,OAAO,EAAE,CAAC;gBAChE,sBAAsB,CAAC,EAAE,OAAO,EAAE,QAAQ,CAAC,OAAO,EAAE,CAAC;gBACrD,oBAAoB,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;gBAC3C,0BAA0B,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;gBACjD,gCAAgC,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;gBACvD,4BAA4B,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;aAClB,CAAA;YAEnC,MAAM,OAAO,GAAG,gBAAgB,CAAC;gBAC/B,OAAO;gBACP,UAAU,EAAE,OAAO;gBACnB,KAAK;gBACL,MAAM;aACP,CAAC,CAAA;YAEF,MAAM,CAAC,cAAc,CAAC,OAAO,EAAE,kBAAkB,EAAE;gBACjD,KAAK,EAAE,OAAO;gBACd,UAAU,EAAE,KAAK;gBACjB,QAAQ,EAAE,KAAK;gBACf,YAAY,EAAE,KAAK;aACpB,CAAC,CAAA;YAEF,MAAM,UAAU,GAAG,EAAE,OAAO,EAAE,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAA;YAC3E,MAAM,cAAc,GAAG,GAAG,EAAE,CAAC,eAAe,CAAC,OAAO,CAAC,CAAA;YACrD,MAAM,SAAS,GAA0B;gBACvC,wBAAwB,CAAC,UAAU,CAAC;gBACpC,8BAA8B,CAAC,EAAE,GAAG,UAAU,EAAE,cAAc,EAAE,CAAC;gBACjE,gCAAgC,CAAC,UAAU,CAAC;gBAC5C,mCAAmC,CAAC,EAAE,GAAG,UAAU,EAAE,cAAc,EAAE,CAAC;aACvE,CAAA;YAED,MAAM,CAAC,cAAc,CAAC,OAAO,EAAE,gBAAgB,EAAE;gBAC/C,KAAK,EAAE,SAAS;gBAChB,UAAU,EAAE,KAAK;gBACjB,QAAQ,EAAE,KAAK;gBACf,YAAY,EAAE,KAAK;aACpB,CAAC,CAAA;YAEF,QAAQ,CAAC,QAAQ,CAAC;gBAChB,EAAE,EAAE,SAAS;gBACb,OAAO,EAAE,cAAc;gBACvB,YAAY,EAAE,CAAC,OAAO,EAAE,wBAAwB,CAAC;aAClD,CAAC,CAAA;YAEF,MAAM,CAAC,IAAI,CAAC,oBAAoB,EAAE;gBAChC,mBAAmB,EAAE,QAAQ,CAAC,OAAO,CAAC,mBAAmB,CAAC,MAAM;gBAChE,WAAW,EAAE,QAAQ,CAAC,WAAW;aAClC,CAAC,CAAA;QACJ,CAAC;KACF,CAAA;AACH,CAAC,CAAA;AAEH,MAAM,UAAU,iBAAiB,CAAC,OAAgB;IAChD,OAAO,CACH,OAAmC,CAAC,gBAAgB,CAAuC;QAC7F,EAAE,CACH,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Field } from 'payload';
|
|
2
|
+
export interface PolicyFieldsOptions {
|
|
3
|
+
availableDestinationLabels: string[];
|
|
4
|
+
defaultPrivacyNotice: string;
|
|
5
|
+
requireConsentByDefault: boolean;
|
|
6
|
+
defaultRateLimit: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Augments the Form Builder collection's fields with the Throughline policy
|
|
10
|
+
* group: privacy notice, consent toggle and label, spam protection (honeypot
|
|
11
|
+
* + per-form rate limit), allowlist-bound destinations, and an optional
|
|
12
|
+
* submitter-confirmation block.
|
|
13
|
+
*
|
|
14
|
+
* Returns a *new* array; never mutates the input. Form Builder's
|
|
15
|
+
* `formOverrides.fields` callback hands us the default field list.
|
|
16
|
+
*/
|
|
17
|
+
export declare function addFormPolicyFields(baseFields: Field[], options: PolicyFieldsOptions): Field[];
|
|
18
|
+
//# sourceMappingURL=policy-fields.d.ts.map
|