@teleporthq/teleport-plugin-next-workflows 0.44.0 → 0.44.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/__tests__/_helpers/fake-store-database.ts +428 -0
- package/__tests__/_helpers/invoice-store.ts +382 -0
- package/__tests__/email-locale-senders.test.ts +3 -1
- package/__tests__/email-sender-name.test.ts +513 -0
- package/__tests__/graph-utils-topological-order.test.ts +155 -1
- package/__tests__/invoice-billing-details.test.ts +107 -1
- package/__tests__/invoice-data-access-replace.test.ts +236 -2
- package/__tests__/invoice-order-items-settle.test.ts +1 -1
- package/__tests__/invoice-payment-email.test.ts +191 -0
- package/__tests__/invoice-regenerate-email.test.ts +288 -0
- package/__tests__/invoice-regenerate-route.test.ts +9 -7
- package/__tests__/invoice-reservation.test.ts +1 -1
- package/__tests__/invoice-tender.test.ts +1 -1
- package/__tests__/segment-splitter-checkout-order.test.ts +306 -0
- package/__tests__/streaming-on-end-env-filter.test.ts +18 -18
- package/__tests__/streaming-on-end-order.test.ts +387 -0
- package/dist/cjs/account-delete-route-generator.d.ts +1 -0
- package/dist/cjs/account-delete-route-generator.d.ts.map +1 -1
- package/dist/cjs/account-delete-route-generator.js +2 -1
- package/dist/cjs/account-delete-route-generator.js.map +1 -1
- package/dist/cjs/auth-generator.d.ts +1 -0
- package/dist/cjs/auth-generator.d.ts.map +1 -1
- package/dist/cjs/auth-generator.js +2 -1
- package/dist/cjs/auth-generator.js.map +1 -1
- package/dist/cjs/executor-generator.d.ts.map +1 -1
- package/dist/cjs/executor-generator.js +9 -26
- package/dist/cjs/executor-generator.js.map +1 -1
- package/dist/cjs/graph-utils.d.ts +31 -1
- package/dist/cjs/graph-utils.d.ts.map +1 -1
- package/dist/cjs/graph-utils.js +112 -10
- package/dist/cjs/graph-utils.js.map +1 -1
- package/dist/cjs/invoice/api-routes-code.d.ts +12 -3
- package/dist/cjs/invoice/api-routes-code.d.ts.map +1 -1
- package/dist/cjs/invoice/api-routes-code.js +152 -21
- package/dist/cjs/invoice/api-routes-code.js.map +1 -1
- package/dist/cjs/invoice/data-access-code.d.ts.map +1 -1
- package/dist/cjs/invoice/data-access-code.js +133 -21
- package/dist/cjs/invoice/data-access-code.js.map +1 -1
- package/dist/cjs/invoice/email-sender-code.d.ts.map +1 -1
- package/dist/cjs/invoice/email-sender-code.js +15 -25
- package/dist/cjs/invoice/email-sender-code.js.map +1 -1
- package/dist/cjs/invoice/index.js +1 -1
- package/dist/cjs/invoice/index.js.map +1 -1
- package/dist/cjs/invoice/invoice-assembly-code.d.ts.map +1 -1
- package/dist/cjs/invoice/invoice-assembly-code.js +10 -6
- package/dist/cjs/invoice/invoice-assembly-code.js.map +1 -1
- package/dist/cjs/invoice/invoice-html-code.d.ts.map +1 -1
- package/dist/cjs/invoice/invoice-html-code.js +3 -0
- package/dist/cjs/invoice/invoice-html-code.js.map +1 -1
- package/dist/cjs/nodes/email/email-mailgun.d.ts.map +1 -1
- package/dist/cjs/nodes/email/email-mailgun.js +4 -2
- package/dist/cjs/nodes/email/email-mailgun.js.map +1 -1
- package/dist/cjs/nodes/email/email-postmark.d.ts.map +1 -1
- package/dist/cjs/nodes/email/email-postmark.js +5 -2
- package/dist/cjs/nodes/email/email-postmark.js.map +1 -1
- package/dist/cjs/nodes/email/email-resend.d.ts.map +1 -1
- package/dist/cjs/nodes/email/email-resend.js +5 -2
- package/dist/cjs/nodes/email/email-resend.js.map +1 -1
- package/dist/cjs/nodes/email/email-sendgrid.d.ts.map +1 -1
- package/dist/cjs/nodes/email/email-sendgrid.js +8 -2
- package/dist/cjs/nodes/email/email-sendgrid.js.map +1 -1
- package/dist/cjs/nodes/storage/storage-local-remove.d.ts.map +1 -1
- package/dist/cjs/nodes/storage/storage-local-remove.js +2 -0
- package/dist/cjs/nodes/storage/storage-local-remove.js.map +1 -1
- package/dist/cjs/nodes/storage/storage-local-set.d.ts.map +1 -1
- package/dist/cjs/nodes/storage/storage-local-set.js +4 -0
- package/dist/cjs/nodes/storage/storage-local-set.js.map +1 -1
- package/dist/cjs/segment-splitter.d.ts.map +1 -1
- package/dist/cjs/segment-splitter.js +65 -27
- package/dist/cjs/segment-splitter.js.map +1 -1
- package/dist/cjs/transactional-email-code.d.ts.map +1 -1
- package/dist/cjs/transactional-email-code.js +32 -18
- package/dist/cjs/transactional-email-code.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/cjs/workflow-project-plugin.d.ts.map +1 -1
- package/dist/cjs/workflow-project-plugin.js +2 -0
- package/dist/cjs/workflow-project-plugin.js.map +1 -1
- package/dist/esm/account-delete-route-generator.d.ts +1 -0
- package/dist/esm/account-delete-route-generator.d.ts.map +1 -1
- package/dist/esm/account-delete-route-generator.js +2 -1
- package/dist/esm/account-delete-route-generator.js.map +1 -1
- package/dist/esm/auth-generator.d.ts +1 -0
- package/dist/esm/auth-generator.d.ts.map +1 -1
- package/dist/esm/auth-generator.js +2 -1
- package/dist/esm/auth-generator.js.map +1 -1
- package/dist/esm/executor-generator.d.ts.map +1 -1
- package/dist/esm/executor-generator.js +9 -26
- package/dist/esm/executor-generator.js.map +1 -1
- package/dist/esm/graph-utils.d.ts +31 -1
- package/dist/esm/graph-utils.d.ts.map +1 -1
- package/dist/esm/graph-utils.js +109 -9
- package/dist/esm/graph-utils.js.map +1 -1
- package/dist/esm/invoice/api-routes-code.d.ts +12 -3
- package/dist/esm/invoice/api-routes-code.d.ts.map +1 -1
- package/dist/esm/invoice/api-routes-code.js +152 -21
- package/dist/esm/invoice/api-routes-code.js.map +1 -1
- package/dist/esm/invoice/data-access-code.d.ts.map +1 -1
- package/dist/esm/invoice/data-access-code.js +133 -21
- package/dist/esm/invoice/data-access-code.js.map +1 -1
- package/dist/esm/invoice/email-sender-code.d.ts.map +1 -1
- package/dist/esm/invoice/email-sender-code.js +15 -25
- package/dist/esm/invoice/email-sender-code.js.map +1 -1
- package/dist/esm/invoice/index.js +1 -1
- package/dist/esm/invoice/index.js.map +1 -1
- package/dist/esm/invoice/invoice-assembly-code.d.ts.map +1 -1
- package/dist/esm/invoice/invoice-assembly-code.js +10 -6
- package/dist/esm/invoice/invoice-assembly-code.js.map +1 -1
- package/dist/esm/invoice/invoice-html-code.d.ts.map +1 -1
- package/dist/esm/invoice/invoice-html-code.js +3 -0
- package/dist/esm/invoice/invoice-html-code.js.map +1 -1
- package/dist/esm/nodes/email/email-mailgun.d.ts.map +1 -1
- package/dist/esm/nodes/email/email-mailgun.js +4 -2
- package/dist/esm/nodes/email/email-mailgun.js.map +1 -1
- package/dist/esm/nodes/email/email-postmark.d.ts.map +1 -1
- package/dist/esm/nodes/email/email-postmark.js +5 -2
- package/dist/esm/nodes/email/email-postmark.js.map +1 -1
- package/dist/esm/nodes/email/email-resend.d.ts.map +1 -1
- package/dist/esm/nodes/email/email-resend.js +5 -2
- package/dist/esm/nodes/email/email-resend.js.map +1 -1
- package/dist/esm/nodes/email/email-sendgrid.d.ts.map +1 -1
- package/dist/esm/nodes/email/email-sendgrid.js +8 -2
- package/dist/esm/nodes/email/email-sendgrid.js.map +1 -1
- package/dist/esm/nodes/storage/storage-local-remove.d.ts.map +1 -1
- package/dist/esm/nodes/storage/storage-local-remove.js +2 -0
- package/dist/esm/nodes/storage/storage-local-remove.js.map +1 -1
- package/dist/esm/nodes/storage/storage-local-set.d.ts.map +1 -1
- package/dist/esm/nodes/storage/storage-local-set.js +4 -0
- package/dist/esm/nodes/storage/storage-local-set.js.map +1 -1
- package/dist/esm/segment-splitter.d.ts.map +1 -1
- package/dist/esm/segment-splitter.js +66 -28
- package/dist/esm/segment-splitter.js.map +1 -1
- package/dist/esm/transactional-email-code.d.ts.map +1 -1
- package/dist/esm/transactional-email-code.js +32 -18
- package/dist/esm/transactional-email-code.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/workflow-project-plugin.d.ts.map +1 -1
- package/dist/esm/workflow-project-plugin.js +2 -0
- package/dist/esm/workflow-project-plugin.js.map +1 -1
- package/package.json +3 -3
- package/src/account-delete-route-generator.ts +3 -1
- package/src/auth-generator.ts +3 -1
- package/src/executor-generator.ts +9 -26
- package/src/graph-utils.ts +143 -11
- package/src/invoice/api-routes-code.ts +166 -21
- package/src/invoice/data-access-code.ts +139 -21
- package/src/invoice/email-sender-code.ts +15 -25
- package/src/invoice/index.ts +1 -1
- package/src/invoice/invoice-assembly-code.ts +10 -6
- package/src/invoice/invoice-html-code.ts +3 -0
- package/src/nodes/email/email-mailgun.ts +11 -2
- package/src/nodes/email/email-postmark.ts +12 -2
- package/src/nodes/email/email-resend.ts +12 -2
- package/src/nodes/email/email-sendgrid.ts +15 -2
- package/src/nodes/storage/storage-local-remove.ts +4 -0
- package/src/nodes/storage/storage-local-set.ts +6 -0
- package/src/segment-splitter.ts +90 -36
- package/src/transactional-email-code.ts +32 -21
- package/src/workflow-project-plugin.ts +2 -0
|
@@ -35,8 +35,9 @@ ${ProductOptions.generateProductOptionsHelperCode()}
|
|
|
35
35
|
|
|
36
36
|
// The invoice details an order's metadata (JSON text) carries beside its
|
|
37
37
|
// billing columns — the keys \`ORDER_BILLING_METADATA_KEYS\` (teleport-gui)
|
|
38
|
-
// writes: the address in its parts and the buyer's tax or VAT
|
|
39
|
-
// part is '' when the order has none or its metadata is no JSON
|
|
38
|
+
// writes: the address in its parts, the company and the buyer's tax or VAT
|
|
39
|
+
// number. Every part is '' when the order has none or its metadata is no JSON
|
|
40
|
+
// object.
|
|
40
41
|
function readOrderBillingMetadata(raw) {
|
|
41
42
|
var parsed = raw;
|
|
42
43
|
if (typeof raw === 'string') {
|
|
@@ -53,6 +54,7 @@ function readOrderBillingMetadata(raw) {
|
|
|
53
54
|
}
|
|
54
55
|
return {
|
|
55
56
|
taxId: part('billingTaxId'),
|
|
57
|
+
company: part('billingCompany'),
|
|
56
58
|
street: part('billingStreet'),
|
|
57
59
|
city: part('billingCity'),
|
|
58
60
|
postalCode: part('billingPostalCode'),
|
|
@@ -446,10 +448,11 @@ function assembleInvoiceData(body, hydratedOrder, hydratedItems) {
|
|
|
446
448
|
// who checked "bill to a different address" has both on the order.
|
|
447
449
|
//
|
|
448
450
|
// An order whose buyer gave invoice details of their own (a booking's
|
|
449
|
-
// "Add invoice details"
|
|
450
|
-
// in its
|
|
451
|
-
//
|
|
452
|
-
// buyer's
|
|
451
|
+
// "Add invoice details", a store checkout's "Billing Details") carries them
|
|
452
|
+
// in its metadata as well: the address in its parts, the company and the tax
|
|
453
|
+
// or VAT number. Those parts are the invoice's address — never mixed with a
|
|
454
|
+
// shipping address — the company is printed under the buyer's name, and the
|
|
455
|
+
// number is the buyer's VAT.
|
|
453
456
|
var orderRow = orderShippingSource;
|
|
454
457
|
var orderBilling = readOrderBillingMetadata(orderRow.metadata);
|
|
455
458
|
var billedApart = !!(orderBilling.street || orderBilling.city || orderBilling.country);
|
|
@@ -487,6 +490,7 @@ function assembleInvoiceData(body, hydratedOrder, hydratedItems) {
|
|
|
487
490
|
customerZip: body.customerZip || fallbackCustomerZip || '',
|
|
488
491
|
customerCountry: body.customerCountry || fallbackCustomerCountry || '',
|
|
489
492
|
customerVat: body.customerVat || orderBilling.taxId || '',
|
|
493
|
+
customerCompany: body.customerCompany || orderBilling.company || '',
|
|
490
494
|
companyName: pdfGenerator.COMPANY_DETAILS.companyName || '',
|
|
491
495
|
companyAddress: pdfGenerator.COMPANY_DETAILS.companyAddress || '',
|
|
492
496
|
companyCity: pdfGenerator.COMPANY_DETAILS.companyCity || '',
|
|
@@ -361,6 +361,7 @@ function buildInvoiceDataScope(invoiceData) {
|
|
|
361
361
|
|
|
362
362
|
var realCustomer = {
|
|
363
363
|
name: invoiceData.customerName || '',
|
|
364
|
+
company: invoiceData.customerCompany || '',
|
|
364
365
|
email: invoiceData.customerEmail || '',
|
|
365
366
|
address: invoiceData.customerAddress || '',
|
|
366
367
|
city: invoiceData.customerCity || '',
|
|
@@ -371,6 +372,7 @@ function buildInvoiceDataScope(invoiceData) {
|
|
|
371
372
|
};
|
|
372
373
|
// The "Bill to" lines a buyer may leave out show only when they did not
|
|
373
374
|
// (string flags: the template compares strings, as with \`hasShipping\`).
|
|
375
|
+
realCustomer.hasCompany = realCustomer.company ? 'true' : 'false';
|
|
374
376
|
realCustomer.hasZip = realCustomer.zip ? 'true' : 'false';
|
|
375
377
|
realCustomer.hasCountry = realCustomer.country ? 'true' : 'false';
|
|
376
378
|
realCustomer.hasVat = realCustomer.vat ? 'true' : 'false';
|
|
@@ -980,6 +982,7 @@ function buildFallbackInvoiceHtml(scope) {
|
|
|
980
982
|
escapeHtml(comp.city || '') + '<br/>' +
|
|
981
983
|
escapeHtml(comp.email || '') + '</div>');
|
|
982
984
|
parts.push('<div style="flex:1;"><strong>' + escapeHtml(cust.name || '') + '</strong><br/>' +
|
|
985
|
+
(cust.company ? escapeHtml(cust.company) + '<br/>' : '') +
|
|
983
986
|
escapeHtml(cust.email || '') + '<br/>' +
|
|
984
987
|
escapeHtml(cust.address || '') + '<br/>' +
|
|
985
988
|
escapeHtml(cust.city || '') + '</div>');
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
+
import { EmailSender } from '@teleporthq/teleport-shared'
|
|
1
2
|
import { NodeHandlerGenerator, handlerToString } from '../types'
|
|
3
|
+
import { assertHandlerHasNoModuleRefs } from '../analytics/commerce-tracking'
|
|
4
|
+
|
|
5
|
+
// Ambient, not imported: a serialized handler has no module scope, so
|
|
6
|
+
// generateHandler appends the helper's source (EmailSender, teleport-shared).
|
|
7
|
+
declare function __emailSender(sender: EmailSender.EmailSenderInput): EmailSender.EmailSenderParts
|
|
2
8
|
|
|
3
9
|
async function email_mailgun(config: any, context: Record<string, unknown>) {
|
|
4
10
|
const apiKey = config.apiKey
|
|
@@ -25,7 +31,7 @@ async function email_mailgun(config: any, context: Record<string, unknown>) {
|
|
|
25
31
|
}
|
|
26
32
|
const mg = mailgun.client(clientOpts)
|
|
27
33
|
|
|
28
|
-
const fromField =
|
|
34
|
+
const fromField = __emailSender({ from, name: fromName }).header
|
|
29
35
|
const toField = Array.isArray(to) ? to : [to]
|
|
30
36
|
|
|
31
37
|
const msgPayload: Record<string, any> = {
|
|
@@ -61,6 +67,9 @@ export const emailMailgun: NodeHandlerGenerator = {
|
|
|
61
67
|
'form-data': '^4.0.0',
|
|
62
68
|
},
|
|
63
69
|
generateHandler(): string {
|
|
64
|
-
return
|
|
70
|
+
return assertHandlerHasNoModuleRefs(
|
|
71
|
+
handlerToString(email_mailgun) + '\n' + EmailSender.generateEmailSenderHelperCode(),
|
|
72
|
+
'email-mailgun'
|
|
73
|
+
)
|
|
65
74
|
},
|
|
66
75
|
}
|
|
@@ -1,8 +1,15 @@
|
|
|
1
|
+
import { EmailSender } from '@teleporthq/teleport-shared'
|
|
1
2
|
import { NodeHandlerGenerator, handlerToString } from '../types'
|
|
3
|
+
import { assertHandlerHasNoModuleRefs } from '../analytics/commerce-tracking'
|
|
4
|
+
|
|
5
|
+
// Ambient, not imported: a serialized handler has no module scope, so
|
|
6
|
+
// generateHandler appends the helper's source (EmailSender, teleport-shared).
|
|
7
|
+
declare function __emailSender(sender: EmailSender.EmailSenderInput): EmailSender.EmailSenderParts
|
|
2
8
|
|
|
3
9
|
async function email_postmark(config: any, context: Record<string, unknown>) {
|
|
4
10
|
const serverToken = config.serverToken
|
|
5
11
|
const from = config.from
|
|
12
|
+
const fromName = config.fromName
|
|
6
13
|
const to = config.to
|
|
7
14
|
const subject = config.subject
|
|
8
15
|
const body = config.body
|
|
@@ -21,7 +28,7 @@ async function email_postmark(config: any, context: Record<string, unknown>) {
|
|
|
21
28
|
const toField = Array.isArray(to) ? to.join(', ') : to
|
|
22
29
|
|
|
23
30
|
const emailPayload: Record<string, any> = {
|
|
24
|
-
From: from,
|
|
31
|
+
From: __emailSender({ from, name: fromName }).header,
|
|
25
32
|
To: toField,
|
|
26
33
|
Subject: subject,
|
|
27
34
|
HtmlBody: body,
|
|
@@ -58,6 +65,9 @@ export const emailPostmark: NodeHandlerGenerator = {
|
|
|
58
65
|
postmark: '^4.0.0',
|
|
59
66
|
},
|
|
60
67
|
generateHandler(): string {
|
|
61
|
-
return
|
|
68
|
+
return assertHandlerHasNoModuleRefs(
|
|
69
|
+
handlerToString(email_postmark) + '\n' + EmailSender.generateEmailSenderHelperCode(),
|
|
70
|
+
'email-postmark'
|
|
71
|
+
)
|
|
62
72
|
},
|
|
63
73
|
}
|
|
@@ -1,8 +1,15 @@
|
|
|
1
|
+
import { EmailSender } from '@teleporthq/teleport-shared'
|
|
1
2
|
import { NodeHandlerGenerator, handlerToString } from '../types'
|
|
3
|
+
import { assertHandlerHasNoModuleRefs } from '../analytics/commerce-tracking'
|
|
4
|
+
|
|
5
|
+
// Ambient, not imported: a serialized handler has no module scope, so
|
|
6
|
+
// generateHandler appends the helper's source (EmailSender, teleport-shared).
|
|
7
|
+
declare function __emailSender(sender: EmailSender.EmailSenderInput): EmailSender.EmailSenderParts
|
|
2
8
|
|
|
3
9
|
async function email_resend(config: any, context: Record<string, unknown>) {
|
|
4
10
|
const apiKey = config.apiKey
|
|
5
11
|
const from = config.from
|
|
12
|
+
const fromName = config.fromName
|
|
6
13
|
const to = config.to
|
|
7
14
|
const subject = config.subject
|
|
8
15
|
const body = config.body
|
|
@@ -17,7 +24,7 @@ async function email_resend(config: any, context: Record<string, unknown>) {
|
|
|
17
24
|
const resend = new Resend(apiKey)
|
|
18
25
|
|
|
19
26
|
const payload: Record<string, any> = {
|
|
20
|
-
from,
|
|
27
|
+
from: __emailSender({ from, name: fromName }).header,
|
|
21
28
|
to: Array.isArray(to) ? to : [to],
|
|
22
29
|
subject,
|
|
23
30
|
html: body,
|
|
@@ -53,6 +60,9 @@ export const emailResend: NodeHandlerGenerator = {
|
|
|
53
60
|
resend: '^4.0.0',
|
|
54
61
|
},
|
|
55
62
|
generateHandler(): string {
|
|
56
|
-
return
|
|
63
|
+
return assertHandlerHasNoModuleRefs(
|
|
64
|
+
handlerToString(email_resend) + '\n' + EmailSender.generateEmailSenderHelperCode(),
|
|
65
|
+
'email-resend'
|
|
66
|
+
)
|
|
57
67
|
},
|
|
58
68
|
}
|
|
@@ -1,8 +1,15 @@
|
|
|
1
|
+
import { EmailSender } from '@teleporthq/teleport-shared'
|
|
1
2
|
import { NodeHandlerGenerator, handlerToString } from '../types'
|
|
3
|
+
import { assertHandlerHasNoModuleRefs } from '../analytics/commerce-tracking'
|
|
4
|
+
|
|
5
|
+
// Ambient, not imported: a serialized handler has no module scope, so
|
|
6
|
+
// generateHandler appends the helper's source (EmailSender, teleport-shared).
|
|
7
|
+
declare function __emailSender(sender: EmailSender.EmailSenderInput): EmailSender.EmailSenderParts
|
|
2
8
|
|
|
3
9
|
async function email_sendgrid(config: any, context: Record<string, unknown>) {
|
|
4
10
|
const apiKey = config.apiKey
|
|
5
11
|
const from = config.from
|
|
12
|
+
const fromName = config.fromName
|
|
6
13
|
const to = config.to
|
|
7
14
|
const subject = config.subject
|
|
8
15
|
const body = config.body
|
|
@@ -17,9 +24,12 @@ async function email_sendgrid(config: any, context: Record<string, unknown>) {
|
|
|
17
24
|
const sgMail = new MailService()
|
|
18
25
|
sgMail.setApiKey(apiKey)
|
|
19
26
|
|
|
27
|
+
// SendGrid reads a "Name <email>" string's name verbatim, quotes included,
|
|
28
|
+
// so a named sender goes as its two parts.
|
|
29
|
+
const sender = __emailSender({ from, name: fromName })
|
|
20
30
|
const msg: Record<string, any> = {
|
|
21
31
|
to: Array.isArray(to) ? to : [to],
|
|
22
|
-
from,
|
|
32
|
+
from: sender.name ? { email: sender.email, name: sender.name } : sender.email,
|
|
23
33
|
subject,
|
|
24
34
|
html: body,
|
|
25
35
|
}
|
|
@@ -62,6 +72,9 @@ export const emailSendgrid: NodeHandlerGenerator = {
|
|
|
62
72
|
'@sendgrid/mail': '^8.0.0',
|
|
63
73
|
},
|
|
64
74
|
generateHandler(): string {
|
|
65
|
-
return
|
|
75
|
+
return assertHandlerHasNoModuleRefs(
|
|
76
|
+
handlerToString(email_sendgrid) + '\n' + EmailSender.generateEmailSenderHelperCode(),
|
|
77
|
+
'email-sendgrid'
|
|
78
|
+
)
|
|
66
79
|
},
|
|
67
80
|
}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { NodeHandlerGenerator, handlerToString } from '../types'
|
|
2
2
|
|
|
3
|
+
// Same-tab notice of the removal, as storage-local-set gives for a write.
|
|
3
4
|
async function storage_local_remove(config: any, context: Record<string, unknown>) {
|
|
4
5
|
const key = config.key
|
|
5
6
|
|
|
6
7
|
try {
|
|
7
8
|
localStorage.removeItem(key)
|
|
9
|
+
window.dispatchEvent(
|
|
10
|
+
new CustomEvent('teleport:local-storage', { detail: { key, value: null } })
|
|
11
|
+
)
|
|
8
12
|
return { success: true }
|
|
9
13
|
} catch (err: unknown) {
|
|
10
14
|
return { success: false, error: (err as Error).message }
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { NodeHandlerGenerator, handlerToString } from '../types'
|
|
2
2
|
|
|
3
|
+
// A write in the visitor's own tab fires no `storage` event. This one lets page
|
|
4
|
+
// scripts react at once: Google consent mode is granted by the cookie banner's
|
|
5
|
+
// Accept, which is this step (teleport-project-generator-next google-consent-mode).
|
|
3
6
|
async function storage_local_set(config: any, context: Record<string, unknown>) {
|
|
4
7
|
const key = config.key
|
|
5
8
|
const value = config.value
|
|
@@ -7,6 +10,9 @@ async function storage_local_set(config: any, context: Record<string, unknown>)
|
|
|
7
10
|
try {
|
|
8
11
|
const serialized = typeof value === 'string' ? value : JSON.stringify(value)
|
|
9
12
|
localStorage.setItem(key, serialized)
|
|
13
|
+
window.dispatchEvent(
|
|
14
|
+
new CustomEvent('teleport:local-storage', { detail: { key, value: serialized } })
|
|
15
|
+
)
|
|
10
16
|
return { success: true }
|
|
11
17
|
} catch (err: unknown) {
|
|
12
18
|
return { success: false, error: (err as Error).message }
|
package/src/segment-splitter.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { UIDLWorkflow, UIDLWorkflowNode, UIDLWorkflowEdge } from '@teleporthq/teleport-types'
|
|
2
2
|
import { WorkflowSegment, WorkflowExecutionEnv } from './types'
|
|
3
|
-
import { getLoopBodyNodes, getNodeById, getTopologicalOrder } from './graph-utils'
|
|
3
|
+
import { getDependents, getLoopBodyNodes, getNodeById, getTopologicalOrder } from './graph-utils'
|
|
4
4
|
import { nodeRegistry } from './nodes'
|
|
5
5
|
import { AWAIT_RESULT_CONFIG_KEY } from './await-result'
|
|
6
6
|
import { WELCOME_EMAIL_CONFIG_KEYS } from './transactional-email-code'
|
|
@@ -144,25 +144,54 @@ export const splitIntoSegments = (workflow: UIDLWorkflow): WorkflowSegment[] =>
|
|
|
144
144
|
? workflow.edges.filter((e) => !errorBranchIds.has(e.source) && !errorBranchIds.has(e.target))
|
|
145
145
|
: workflow.edges
|
|
146
146
|
|
|
147
|
-
const order = getTopologicalOrder(mainNodes, mainEdges)
|
|
148
147
|
// Loop bodies must execute as a single unit at runtime. The split-by-env
|
|
149
148
|
// walk below would otherwise drop AI/server steps in one segment and the
|
|
150
149
|
// client-only post-processing into another, leaving the loop's body half-
|
|
151
|
-
// executed for every iteration.
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
const
|
|
156
|
-
|
|
150
|
+
// executed for every iteration. Every loop + body gets a single forced env
|
|
151
|
+
// (server if any body or the loop itself needs server, else client), and the
|
|
152
|
+
// topo order is rearranged so all body nodes sit immediately after their
|
|
153
|
+
// owning loop, so the env walk emits one segment for the loop + body.
|
|
154
|
+
const forcedEnvByNodeId = resolveLoopClusterEnvs(mainNodes, mainEdges)
|
|
155
|
+
const order = getTopologicalOrder(
|
|
157
156
|
mainNodes,
|
|
158
|
-
mainEdges
|
|
157
|
+
mainEdges,
|
|
158
|
+
(node) => forcedEnvByNodeId.get(node.id) ?? resolveFixedExecutionEnv(node)
|
|
159
159
|
)
|
|
160
|
+
const reorderedOrder = hoistLoopBodies(order, mainNodes, mainEdges)
|
|
160
161
|
const segments: WorkflowSegment[] = []
|
|
161
162
|
let currentEnv: WorkflowExecutionEnv | null = null
|
|
162
163
|
let currentNodeIds: string[] = []
|
|
163
164
|
let segClientCount = 0
|
|
164
165
|
let segServerCount = 0
|
|
165
166
|
|
|
167
|
+
// A server route runs its segment's nodes by stepNumber, never by edge, so a
|
|
168
|
+
// node must not join a segment holding one of its inputs at a later step (a
|
|
169
|
+
// join also reached by a shorter path, a value read across branches): the
|
|
170
|
+
// route would run it first. Loop bodies run inside their loop, at the loop's
|
|
171
|
+
// step, and never leave its segment.
|
|
172
|
+
const inputsById = new Map<string, string[]>()
|
|
173
|
+
Object.entries(getDependents(mainNodes, mainEdges)).forEach(([source, dependents]) =>
|
|
174
|
+
dependents.forEach((id) => inputsById.set(id, [...(inputsById.get(id) || []), source]))
|
|
175
|
+
)
|
|
176
|
+
const loopStepByBodyNodeId = new Map<string, number>()
|
|
177
|
+
mainNodes
|
|
178
|
+
.filter((n) => n.type === 'general-loop')
|
|
179
|
+
.forEach((loop) =>
|
|
180
|
+
getLoopBodyNodes(loop.id, mainEdges).forEach((id) =>
|
|
181
|
+
loopStepByBodyNodeId.set(
|
|
182
|
+
id,
|
|
183
|
+
Math.min(loopStepByBodyNodeId.get(id) ?? Infinity, loop.stepNumber || 0)
|
|
184
|
+
)
|
|
185
|
+
)
|
|
186
|
+
)
|
|
187
|
+
const runsAtStep = (nodeId: string): number =>
|
|
188
|
+
loopStepByBodyNodeId.get(nodeId) ?? (getNodeById(mainNodes, nodeId)?.stepNumber || 0)
|
|
189
|
+
const readsALaterStepOfThisSegment = (node: UIDLWorkflowNode): boolean =>
|
|
190
|
+
!loopStepByBodyNodeId.has(node.id) &&
|
|
191
|
+
(inputsById.get(node.id) || []).some(
|
|
192
|
+
(inputId) => currentNodeIds.includes(inputId) && runsAtStep(inputId) > (node.stepNumber || 0)
|
|
193
|
+
)
|
|
194
|
+
|
|
166
195
|
const flushSegment = () => {
|
|
167
196
|
if (currentNodeIds.length === 0 || currentEnv === null) {
|
|
168
197
|
return
|
|
@@ -196,7 +225,10 @@ export const splitIntoSegments = (workflow: UIDLWorkflow): WorkflowSegment[] =>
|
|
|
196
225
|
const forced = forcedEnvByNodeId.get(nodeId)
|
|
197
226
|
const nodeEnv = forced ?? resolveExecutionEnv(node, currentEnv)
|
|
198
227
|
|
|
199
|
-
if (
|
|
228
|
+
if (
|
|
229
|
+
currentEnv !== null &&
|
|
230
|
+
(nodeEnv !== currentEnv || (nodeEnv === 'server' && readsALaterStepOfThisSegment(node)))
|
|
231
|
+
) {
|
|
200
232
|
flushSegment()
|
|
201
233
|
}
|
|
202
234
|
currentEnv = nodeEnv
|
|
@@ -208,10 +240,7 @@ export const splitIntoSegments = (workflow: UIDLWorkflow): WorkflowSegment[] =>
|
|
|
208
240
|
}
|
|
209
241
|
|
|
210
242
|
/**
|
|
211
|
-
*
|
|
212
|
-
* directly after the loop, and pick a single execution env for the
|
|
213
|
-
* (loop + body) cluster. Nested loops are processed inner-first so the outer
|
|
214
|
-
* loop's body window already contains the inner body in the right order.
|
|
243
|
+
* The single execution env of every (loop + body) cluster.
|
|
215
244
|
*
|
|
216
245
|
* Why an explicit env override is needed: a body that mixes universal /
|
|
217
246
|
* server / client nodes (e.g. fetch → AI prompt → custom-js with
|
|
@@ -224,19 +253,58 @@ export const splitIntoSegments = (workflow: UIDLWorkflow): WorkflowSegment[] =>
|
|
|
224
253
|
* runs the same string of JS regardless of where it executes); the inverse
|
|
225
254
|
* is not true for AI / HTTP-with-secret nodes. When no member is server, we
|
|
226
255
|
* keep the cluster on the client so DOM- or window-bound bodies still work.
|
|
256
|
+
* A node of nested loops takes its outermost loop's env, which is server
|
|
257
|
+
* whenever an inner loop's is.
|
|
227
258
|
*/
|
|
228
|
-
const
|
|
229
|
-
order: string[],
|
|
259
|
+
const resolveLoopClusterEnvs = (
|
|
230
260
|
mainNodes: UIDLWorkflowNode[],
|
|
231
261
|
mainEdges: UIDLWorkflowEdge[]
|
|
232
|
-
): {
|
|
233
|
-
reorderedOrder: string[]
|
|
234
|
-
forcedEnvByNodeId: Map<string, WorkflowExecutionEnv>
|
|
235
|
-
} => {
|
|
262
|
+
): Map<string, WorkflowExecutionEnv> => {
|
|
236
263
|
const forcedEnvByNodeId = new Map<string, WorkflowExecutionEnv>()
|
|
264
|
+
for (const loop of mainNodes.filter((n) => n.type === 'general-loop')) {
|
|
265
|
+
const bodyIds = getLoopBodyNodes(loop.id, mainEdges)
|
|
266
|
+
if (bodyIds.length === 0) {
|
|
267
|
+
continue
|
|
268
|
+
}
|
|
269
|
+
const clusterMembers = [loop.id, ...bodyIds]
|
|
270
|
+
.map((id) => getNodeById(mainNodes, id))
|
|
271
|
+
.filter((n): n is UIDLWorkflowNode => Boolean(n))
|
|
272
|
+
const clusterEnv: WorkflowExecutionEnv = clusterMembers.some(
|
|
273
|
+
(member) => resolveExecutionEnv(member, null) === 'server'
|
|
274
|
+
)
|
|
275
|
+
? 'server'
|
|
276
|
+
: 'client'
|
|
277
|
+
for (const member of clusterMembers) {
|
|
278
|
+
if (forcedEnvByNodeId.get(member.id) !== 'server') {
|
|
279
|
+
forcedEnvByNodeId.set(member.id, clusterEnv)
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return forcedEnvByNodeId
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Where a node runs whatever ran before it, or `null` for a node that takes the
|
|
288
|
+
* env of the node before it (an if-statement): it fits either side.
|
|
289
|
+
*/
|
|
290
|
+
const resolveFixedExecutionEnv = (node: UIDLWorkflowNode): WorkflowExecutionEnv | null => {
|
|
291
|
+
const env = resolveExecutionEnv(node, 'server')
|
|
292
|
+
return env === resolveExecutionEnv(node, 'client') ? env : null
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Re-arrange the topological node order so every loop's body nodes sit
|
|
297
|
+
* directly after the loop. Nested loops are processed inner-first so the outer
|
|
298
|
+
* loop's body window already contains the inner body in the right order.
|
|
299
|
+
*/
|
|
300
|
+
const hoistLoopBodies = (
|
|
301
|
+
order: string[],
|
|
302
|
+
mainNodes: UIDLWorkflowNode[],
|
|
303
|
+
mainEdges: UIDLWorkflowEdge[]
|
|
304
|
+
): string[] => {
|
|
237
305
|
const loopNodes = mainNodes.filter((n) => n.type === 'general-loop')
|
|
238
306
|
if (loopNodes.length === 0) {
|
|
239
|
-
return
|
|
307
|
+
return order
|
|
240
308
|
}
|
|
241
309
|
|
|
242
310
|
// Process inner loops before outer ones: an inner loop's body is a subset
|
|
@@ -253,20 +321,6 @@ const enforceLoopBodyIntegrity = (
|
|
|
253
321
|
}
|
|
254
322
|
|
|
255
323
|
const clusterIds = new Set<string>([loopId, ...bodyIds])
|
|
256
|
-
const clusterMembers = Array.from(clusterIds)
|
|
257
|
-
.map((id) => getNodeById(mainNodes, id))
|
|
258
|
-
.filter((n): n is UIDLWorkflowNode => Boolean(n))
|
|
259
|
-
|
|
260
|
-
let clusterEnv: WorkflowExecutionEnv = 'client'
|
|
261
|
-
for (const member of clusterMembers) {
|
|
262
|
-
if (resolveExecutionEnv(member, null) === 'server') {
|
|
263
|
-
clusterEnv = 'server'
|
|
264
|
-
break
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
for (const member of clusterMembers) {
|
|
268
|
-
forcedEnvByNodeId.set(member.id, clusterEnv)
|
|
269
|
-
}
|
|
270
324
|
|
|
271
325
|
// Hoist body nodes (preserving their existing relative order) immediately
|
|
272
326
|
// after the loop. Other nodes keep their relative position.
|
|
@@ -283,7 +337,7 @@ const enforceLoopBodyIntegrity = (
|
|
|
283
337
|
]
|
|
284
338
|
}
|
|
285
339
|
|
|
286
|
-
return
|
|
340
|
+
return working
|
|
287
341
|
}
|
|
288
342
|
|
|
289
343
|
/**
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* every route stays byte-for-byte consistent.
|
|
6
6
|
*/
|
|
7
7
|
import { UIDLLocalizedEmailTemplates } from '@teleporthq/teleport-types'
|
|
8
|
+
import { EmailSender } from '@teleporthq/teleport-shared'
|
|
8
9
|
import { getEmailProviderDependencies } from './invoice/email-sender-code'
|
|
9
10
|
import { EMAIL_LOCALE_FILE_NAME, EMAIL_LOCALE_PATH } from './email-locale/email-locale-scope'
|
|
10
11
|
|
|
@@ -39,24 +40,28 @@ export interface TransactionalEmailLedger {
|
|
|
39
40
|
relativePrefix: string
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
// Provider-specific `__sendProviderEmail({ from, to, subject, html,
|
|
43
|
-
// tokenValues?, userId? })
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
//
|
|
43
|
+
// Provider-specific `__sendProviderEmail({ from, fromName?, to, subject, html,
|
|
44
|
+
// apiKey, tokenValues?, userId? })`, preceded by the `__emailSender` helper that
|
|
45
|
+
// names the sender. No attachment (unlike the invoice sender). When no provider
|
|
46
|
+
// is configured the function is a no-op so the route stays self-contained and
|
|
47
|
+
// never throws. With `ledger`, the provider function is emitted as
|
|
48
|
+
// `__sendProviderEmailRaw` and `__sendProviderEmail` records every attempt
|
|
49
|
+
// (success or throw) before returning / rethrowing.
|
|
48
50
|
export const generateProviderSendFunction = (
|
|
49
51
|
provider?: string | null,
|
|
50
52
|
ledger?: TransactionalEmailLedger
|
|
51
53
|
): string => {
|
|
52
54
|
const raw = generateRawProviderSendFunction(provider)
|
|
53
|
-
if (!
|
|
55
|
+
if (!provider || !SUPPORTED_EMAIL_PROVIDERS.has(provider)) {
|
|
54
56
|
return raw
|
|
55
57
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
const senderHelper = EmailSender.generateEmailSenderHelperCode()
|
|
59
|
+
if (!ledger) {
|
|
60
|
+
return `${senderHelper}
|
|
61
|
+
${raw}`
|
|
62
|
+
}
|
|
63
|
+
return `${senderHelper}
|
|
64
|
+
${raw.replace('async function __sendProviderEmail(', 'async function __sendProviderEmailRaw(')}
|
|
60
65
|
|
|
61
66
|
var __sentEmailLog = require('${ledger.relativePrefix}/utils/email/sent-email-log');
|
|
62
67
|
|
|
@@ -67,12 +72,14 @@ async function __sendProviderEmail(msg) {
|
|
|
67
72
|
} catch (err) {
|
|
68
73
|
failure = err;
|
|
69
74
|
}
|
|
75
|
+
var sender = __emailSender({ from: msg.from, name: msg.fromName });
|
|
70
76
|
// Recorded in the background; the route settles the ledger before it replies.
|
|
71
77
|
__sentEmailLog.recordSentEmail({
|
|
72
78
|
emailType: ${JSON.stringify(ledger.emailType)},
|
|
73
79
|
audience: 'customer',
|
|
74
80
|
to: msg.to,
|
|
75
|
-
from:
|
|
81
|
+
from: sender.email,
|
|
82
|
+
fromName: sender.name,
|
|
76
83
|
subject: msg.subject,
|
|
77
84
|
html: msg.html,
|
|
78
85
|
payload: msg.tokenValues,
|
|
@@ -93,20 +100,26 @@ const generateRawProviderSendFunction = (provider?: string | null): string => {
|
|
|
93
100
|
return `async function __sendProviderEmail(msg) {
|
|
94
101
|
var Resend = require('resend').Resend;
|
|
95
102
|
var resend = new Resend(msg.apiKey);
|
|
96
|
-
var
|
|
103
|
+
var sender = __emailSender({ from: msg.from, name: msg.fromName });
|
|
104
|
+
var result = await resend.emails.send({ from: sender.header, to: [msg.to], subject: msg.subject, html: msg.html });
|
|
97
105
|
if (result && result.error) { throw new Error(result.error.message || 'Failed to send email'); }
|
|
98
106
|
}`
|
|
99
107
|
case 'sendgrid':
|
|
108
|
+
// SendGrid reads a "Name <email>" string's name verbatim, quotes included,
|
|
109
|
+
// so a named sender goes as its two parts.
|
|
100
110
|
return `async function __sendProviderEmail(msg) {
|
|
101
111
|
var sgMail = require('@sendgrid/mail');
|
|
102
112
|
sgMail.setApiKey(msg.apiKey);
|
|
103
|
-
|
|
113
|
+
var sender = __emailSender({ from: msg.from, name: msg.fromName });
|
|
114
|
+
var from = sender.name ? { email: sender.email, name: sender.name } : sender.email;
|
|
115
|
+
await sgMail.send({ to: msg.to, from: from, subject: msg.subject, html: msg.html });
|
|
104
116
|
}`
|
|
105
117
|
case 'postmark':
|
|
106
118
|
return `async function __sendProviderEmail(msg) {
|
|
107
119
|
var postmark = require('postmark');
|
|
108
120
|
var client = new postmark.ServerClient(msg.apiKey);
|
|
109
|
-
|
|
121
|
+
var sender = __emailSender({ from: msg.from, name: msg.fromName });
|
|
122
|
+
await client.sendEmail({ From: sender.header, To: msg.to, Subject: msg.subject, HtmlBody: msg.html });
|
|
110
123
|
}`
|
|
111
124
|
case 'mailgun':
|
|
112
125
|
return `async function __sendProviderEmail(msg) {
|
|
@@ -116,7 +129,8 @@ const generateRawProviderSendFunction = (provider?: string | null): string => {
|
|
|
116
129
|
if (!domain) { throw new Error('Mailgun domain not configured'); }
|
|
117
130
|
var mailgun = new Mailgun(formData);
|
|
118
131
|
var mg = mailgun.client({ username: 'api', key: msg.apiKey });
|
|
119
|
-
|
|
132
|
+
var sender = __emailSender({ from: msg.from, name: msg.fromName });
|
|
133
|
+
await mg.messages.create(domain, { from: sender.header, to: [msg.to], subject: msg.subject, html: msg.html });
|
|
120
134
|
}`
|
|
121
135
|
case 'mailersend':
|
|
122
136
|
return `async function __sendProviderEmail(msg) {
|
|
@@ -125,11 +139,8 @@ const generateRawProviderSendFunction = (provider?: string | null): string => {
|
|
|
125
139
|
var Sender = require('mailersend').Sender;
|
|
126
140
|
var Recipient = require('mailersend').Recipient;
|
|
127
141
|
var mailerSend = new MailerSend({ apiKey: msg.apiKey });
|
|
128
|
-
var
|
|
129
|
-
var
|
|
130
|
-
var fromEmail = m ? m[2].trim() : fromStr;
|
|
131
|
-
var fromName = m ? m[1].trim() : '';
|
|
132
|
-
var sender = new Sender(fromEmail, fromName || fromEmail);
|
|
142
|
+
var from = __emailSender({ from: msg.from, name: msg.fromName });
|
|
143
|
+
var sender = new Sender(from.email, from.name || from.email);
|
|
133
144
|
var params = new EmailParams().setFrom(sender).setTo([new Recipient(msg.to)]).setSubject(msg.subject).setHtml(msg.html);
|
|
134
145
|
await mailerSend.email.send(params);
|
|
135
146
|
}`
|
|
@@ -783,6 +783,7 @@ export class NextWorkflowProjectPlugin implements ProjectPlugin {
|
|
|
783
783
|
authUsersTableName: resolveAuthUsersTableName(uidl.authentication),
|
|
784
784
|
emailProvider,
|
|
785
785
|
fromEmail: typeof deleteConfig.from === 'string' ? deleteConfig.from : '',
|
|
786
|
+
fromName: typeof deleteConfig.fromName === 'string' ? deleteConfig.fromName : '',
|
|
786
787
|
emailSecretEnvName,
|
|
787
788
|
emailSubject: typeof deleteConfig.subject === 'string' ? deleteConfig.subject : '',
|
|
788
789
|
emailBodyHtml: typeof deleteConfig.body === 'string' ? deleteConfig.body : '',
|
|
@@ -1807,6 +1808,7 @@ module.exports = __customNodeRegistry;
|
|
|
1807
1808
|
const signupRouteCode = generateSignupRouteFile(auth, {
|
|
1808
1809
|
emailProvider: welcomeProvider,
|
|
1809
1810
|
fromEmail: typeof signupConfig.from === 'string' ? signupConfig.from : '',
|
|
1811
|
+
fromName: typeof signupConfig.fromName === 'string' ? signupConfig.fromName : '',
|
|
1810
1812
|
emailSecretEnvName: welcomeSecretEnvName,
|
|
1811
1813
|
emailSubject: typeof signupConfig.subject === 'string' ? signupConfig.subject : '',
|
|
1812
1814
|
emailBodyHtml: typeof signupConfig.body === 'string' ? signupConfig.body : '',
|