@teleporthq/teleport-plugin-next-workflows 0.43.75 → 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.
Files changed (248) hide show
  1. package/__tests__/_helpers/fake-store-database.ts +428 -0
  2. package/__tests__/_helpers/invoice-store.ts +382 -0
  3. package/__tests__/booking-calendar-feed-route.test.ts +494 -0
  4. package/__tests__/custom-node-result-taken-branch.test.ts +107 -0
  5. package/__tests__/data-api-date-parser.test.ts +45 -0
  6. package/__tests__/email-locale-senders.test.ts +3 -1
  7. package/__tests__/email-sender-name.test.ts +513 -0
  8. package/__tests__/graph-utils-topological-order.test.ts +155 -1
  9. package/__tests__/invoice-billing-details.test.ts +384 -0
  10. package/__tests__/invoice-data-access-replace.test.ts +236 -2
  11. package/__tests__/invoice-order-items-settle.test.ts +1 -1
  12. package/__tests__/invoice-payment-email.test.ts +191 -0
  13. package/__tests__/invoice-regenerate-email.test.ts +288 -0
  14. package/__tests__/invoice-regenerate-route.test.ts +9 -7
  15. package/__tests__/invoice-reservation.test.ts +1 -1
  16. package/__tests__/invoice-tender.test.ts +1 -1
  17. package/__tests__/middleware-admin-panel-roles.test.ts +127 -0
  18. package/__tests__/page-arrival-remount.test.ts +247 -0
  19. package/__tests__/payment-drivers-module.test.ts +19 -0
  20. package/__tests__/raw-query-error-code.test.ts +47 -0
  21. package/__tests__/runtime-template-token-resolution.test.ts +16 -0
  22. package/__tests__/segment-reply-changed-keys.test.ts +31 -0
  23. package/__tests__/segment-route-skipped-branch.test.ts +101 -0
  24. package/__tests__/segment-splitter-checkout-order.test.ts +306 -0
  25. package/__tests__/streaming-on-end-env-filter.test.ts +18 -18
  26. package/__tests__/streaming-on-end-order.test.ts +387 -0
  27. package/dist/cjs/account-delete-route-generator.d.ts +1 -0
  28. package/dist/cjs/account-delete-route-generator.d.ts.map +1 -1
  29. package/dist/cjs/account-delete-route-generator.js +2 -1
  30. package/dist/cjs/account-delete-route-generator.js.map +1 -1
  31. package/dist/cjs/api-route-generator.d.ts.map +1 -1
  32. package/dist/cjs/api-route-generator.js +23 -13
  33. package/dist/cjs/api-route-generator.js.map +1 -1
  34. package/dist/cjs/auth-generator.d.ts +1 -0
  35. package/dist/cjs/auth-generator.d.ts.map +1 -1
  36. package/dist/cjs/auth-generator.js +77 -28
  37. package/dist/cjs/auth-generator.js.map +1 -1
  38. package/dist/cjs/booking-calendar-feed-route-generator.d.ts +28 -0
  39. package/dist/cjs/booking-calendar-feed-route-generator.d.ts.map +1 -0
  40. package/dist/cjs/booking-calendar-feed-route-generator.js +296 -0
  41. package/dist/cjs/booking-calendar-feed-route-generator.js.map +1 -0
  42. package/dist/cjs/data-api-route-generator.d.ts.map +1 -1
  43. package/dist/cjs/data-api-route-generator.js +6 -1
  44. package/dist/cjs/data-api-route-generator.js.map +1 -1
  45. package/dist/cjs/executor-generator.d.ts.map +1 -1
  46. package/dist/cjs/executor-generator.js +39 -28
  47. package/dist/cjs/executor-generator.js.map +1 -1
  48. package/dist/cjs/graph-utils.d.ts +31 -1
  49. package/dist/cjs/graph-utils.d.ts.map +1 -1
  50. package/dist/cjs/graph-utils.js +112 -10
  51. package/dist/cjs/graph-utils.js.map +1 -1
  52. package/dist/cjs/index.d.ts +1 -1
  53. package/dist/cjs/index.d.ts.map +1 -1
  54. package/dist/cjs/index.js +2 -1
  55. package/dist/cjs/index.js.map +1 -1
  56. package/dist/cjs/invoice/api-routes-code.d.ts +12 -3
  57. package/dist/cjs/invoice/api-routes-code.d.ts.map +1 -1
  58. package/dist/cjs/invoice/api-routes-code.js +152 -21
  59. package/dist/cjs/invoice/api-routes-code.js.map +1 -1
  60. package/dist/cjs/invoice/data-access-code.d.ts.map +1 -1
  61. package/dist/cjs/invoice/data-access-code.js +133 -21
  62. package/dist/cjs/invoice/data-access-code.js.map +1 -1
  63. package/dist/cjs/invoice/email-sender-code.d.ts.map +1 -1
  64. package/dist/cjs/invoice/email-sender-code.js +15 -25
  65. package/dist/cjs/invoice/email-sender-code.js.map +1 -1
  66. package/dist/cjs/invoice/index.js +1 -1
  67. package/dist/cjs/invoice/index.js.map +1 -1
  68. package/dist/cjs/invoice/invoice-assembly-code.d.ts.map +1 -1
  69. package/dist/cjs/invoice/invoice-assembly-code.js +50 -6
  70. package/dist/cjs/invoice/invoice-assembly-code.js.map +1 -1
  71. package/dist/cjs/invoice/invoice-html-code.d.ts.map +1 -1
  72. package/dist/cjs/invoice/invoice-html-code.js +13 -2
  73. package/dist/cjs/invoice/invoice-html-code.js.map +1 -1
  74. package/dist/cjs/nodes/data/data-raw-query.d.ts.map +1 -1
  75. package/dist/cjs/nodes/data/data-raw-query.js +11 -1
  76. package/dist/cjs/nodes/data/data-raw-query.js.map +1 -1
  77. package/dist/cjs/nodes/email/email-mailgun.d.ts.map +1 -1
  78. package/dist/cjs/nodes/email/email-mailgun.js +4 -2
  79. package/dist/cjs/nodes/email/email-mailgun.js.map +1 -1
  80. package/dist/cjs/nodes/email/email-postmark.d.ts.map +1 -1
  81. package/dist/cjs/nodes/email/email-postmark.js +5 -2
  82. package/dist/cjs/nodes/email/email-postmark.js.map +1 -1
  83. package/dist/cjs/nodes/email/email-resend.d.ts.map +1 -1
  84. package/dist/cjs/nodes/email/email-resend.js +5 -2
  85. package/dist/cjs/nodes/email/email-resend.js.map +1 -1
  86. package/dist/cjs/nodes/email/email-sendgrid.d.ts.map +1 -1
  87. package/dist/cjs/nodes/email/email-sendgrid.js +8 -2
  88. package/dist/cjs/nodes/email/email-sendgrid.js.map +1 -1
  89. package/dist/cjs/nodes/general/general-custom-js.d.ts.map +1 -1
  90. package/dist/cjs/nodes/general/general-custom-js.js +11 -0
  91. package/dist/cjs/nodes/general/general-custom-js.js.map +1 -1
  92. package/dist/cjs/nodes/storage/storage-local-remove.d.ts.map +1 -1
  93. package/dist/cjs/nodes/storage/storage-local-remove.js +2 -0
  94. package/dist/cjs/nodes/storage/storage-local-remove.js.map +1 -1
  95. package/dist/cjs/nodes/storage/storage-local-set.d.ts.map +1 -1
  96. package/dist/cjs/nodes/storage/storage-local-set.js +4 -0
  97. package/dist/cjs/nodes/storage/storage-local-set.js.map +1 -1
  98. package/dist/cjs/page-arrival.d.ts +10 -0
  99. package/dist/cjs/page-arrival.d.ts.map +1 -0
  100. package/dist/cjs/page-arrival.js +86 -0
  101. package/dist/cjs/page-arrival.js.map +1 -0
  102. package/dist/cjs/payments/index.d.ts +10 -4
  103. package/dist/cjs/payments/index.d.ts.map +1 -1
  104. package/dist/cjs/payments/index.js +18 -7
  105. package/dist/cjs/payments/index.js.map +1 -1
  106. package/dist/cjs/segment-splitter.d.ts.map +1 -1
  107. package/dist/cjs/segment-splitter.js +65 -27
  108. package/dist/cjs/segment-splitter.js.map +1 -1
  109. package/dist/cjs/transactional-email-code.d.ts.map +1 -1
  110. package/dist/cjs/transactional-email-code.js +32 -18
  111. package/dist/cjs/transactional-email-code.js.map +1 -1
  112. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  113. package/dist/cjs/workflow-component-plugin.d.ts.map +1 -1
  114. package/dist/cjs/workflow-component-plugin.js +35 -149
  115. package/dist/cjs/workflow-component-plugin.js.map +1 -1
  116. package/dist/cjs/workflow-project-plugin.d.ts.map +1 -1
  117. package/dist/cjs/workflow-project-plugin.js +16 -4
  118. package/dist/cjs/workflow-project-plugin.js.map +1 -1
  119. package/dist/cjs/workflow-scope.d.ts +19 -0
  120. package/dist/cjs/workflow-scope.d.ts.map +1 -0
  121. package/dist/cjs/workflow-scope.js +147 -0
  122. package/dist/cjs/workflow-scope.js.map +1 -0
  123. package/dist/esm/account-delete-route-generator.d.ts +1 -0
  124. package/dist/esm/account-delete-route-generator.d.ts.map +1 -1
  125. package/dist/esm/account-delete-route-generator.js +2 -1
  126. package/dist/esm/account-delete-route-generator.js.map +1 -1
  127. package/dist/esm/api-route-generator.d.ts.map +1 -1
  128. package/dist/esm/api-route-generator.js +23 -13
  129. package/dist/esm/api-route-generator.js.map +1 -1
  130. package/dist/esm/auth-generator.d.ts +1 -0
  131. package/dist/esm/auth-generator.d.ts.map +1 -1
  132. package/dist/esm/auth-generator.js +77 -28
  133. package/dist/esm/auth-generator.js.map +1 -1
  134. package/dist/esm/booking-calendar-feed-route-generator.d.ts +28 -0
  135. package/dist/esm/booking-calendar-feed-route-generator.d.ts.map +1 -0
  136. package/dist/esm/booking-calendar-feed-route-generator.js +291 -0
  137. package/dist/esm/booking-calendar-feed-route-generator.js.map +1 -0
  138. package/dist/esm/data-api-route-generator.d.ts.map +1 -1
  139. package/dist/esm/data-api-route-generator.js +6 -1
  140. package/dist/esm/data-api-route-generator.js.map +1 -1
  141. package/dist/esm/executor-generator.d.ts.map +1 -1
  142. package/dist/esm/executor-generator.js +39 -28
  143. package/dist/esm/executor-generator.js.map +1 -1
  144. package/dist/esm/graph-utils.d.ts +31 -1
  145. package/dist/esm/graph-utils.d.ts.map +1 -1
  146. package/dist/esm/graph-utils.js +109 -9
  147. package/dist/esm/graph-utils.js.map +1 -1
  148. package/dist/esm/index.d.ts +1 -1
  149. package/dist/esm/index.d.ts.map +1 -1
  150. package/dist/esm/index.js +1 -1
  151. package/dist/esm/index.js.map +1 -1
  152. package/dist/esm/invoice/api-routes-code.d.ts +12 -3
  153. package/dist/esm/invoice/api-routes-code.d.ts.map +1 -1
  154. package/dist/esm/invoice/api-routes-code.js +152 -21
  155. package/dist/esm/invoice/api-routes-code.js.map +1 -1
  156. package/dist/esm/invoice/data-access-code.d.ts.map +1 -1
  157. package/dist/esm/invoice/data-access-code.js +133 -21
  158. package/dist/esm/invoice/data-access-code.js.map +1 -1
  159. package/dist/esm/invoice/email-sender-code.d.ts.map +1 -1
  160. package/dist/esm/invoice/email-sender-code.js +15 -25
  161. package/dist/esm/invoice/email-sender-code.js.map +1 -1
  162. package/dist/esm/invoice/index.js +1 -1
  163. package/dist/esm/invoice/index.js.map +1 -1
  164. package/dist/esm/invoice/invoice-assembly-code.d.ts.map +1 -1
  165. package/dist/esm/invoice/invoice-assembly-code.js +50 -6
  166. package/dist/esm/invoice/invoice-assembly-code.js.map +1 -1
  167. package/dist/esm/invoice/invoice-html-code.d.ts.map +1 -1
  168. package/dist/esm/invoice/invoice-html-code.js +13 -2
  169. package/dist/esm/invoice/invoice-html-code.js.map +1 -1
  170. package/dist/esm/nodes/data/data-raw-query.d.ts.map +1 -1
  171. package/dist/esm/nodes/data/data-raw-query.js +11 -1
  172. package/dist/esm/nodes/data/data-raw-query.js.map +1 -1
  173. package/dist/esm/nodes/email/email-mailgun.d.ts.map +1 -1
  174. package/dist/esm/nodes/email/email-mailgun.js +4 -2
  175. package/dist/esm/nodes/email/email-mailgun.js.map +1 -1
  176. package/dist/esm/nodes/email/email-postmark.d.ts.map +1 -1
  177. package/dist/esm/nodes/email/email-postmark.js +5 -2
  178. package/dist/esm/nodes/email/email-postmark.js.map +1 -1
  179. package/dist/esm/nodes/email/email-resend.d.ts.map +1 -1
  180. package/dist/esm/nodes/email/email-resend.js +5 -2
  181. package/dist/esm/nodes/email/email-resend.js.map +1 -1
  182. package/dist/esm/nodes/email/email-sendgrid.d.ts.map +1 -1
  183. package/dist/esm/nodes/email/email-sendgrid.js +8 -2
  184. package/dist/esm/nodes/email/email-sendgrid.js.map +1 -1
  185. package/dist/esm/nodes/general/general-custom-js.d.ts.map +1 -1
  186. package/dist/esm/nodes/general/general-custom-js.js +11 -0
  187. package/dist/esm/nodes/general/general-custom-js.js.map +1 -1
  188. package/dist/esm/nodes/storage/storage-local-remove.d.ts.map +1 -1
  189. package/dist/esm/nodes/storage/storage-local-remove.js +2 -0
  190. package/dist/esm/nodes/storage/storage-local-remove.js.map +1 -1
  191. package/dist/esm/nodes/storage/storage-local-set.d.ts.map +1 -1
  192. package/dist/esm/nodes/storage/storage-local-set.js +4 -0
  193. package/dist/esm/nodes/storage/storage-local-set.js.map +1 -1
  194. package/dist/esm/page-arrival.d.ts +10 -0
  195. package/dist/esm/page-arrival.d.ts.map +1 -0
  196. package/dist/esm/page-arrival.js +81 -0
  197. package/dist/esm/page-arrival.js.map +1 -0
  198. package/dist/esm/payments/index.d.ts +10 -4
  199. package/dist/esm/payments/index.d.ts.map +1 -1
  200. package/dist/esm/payments/index.js +16 -6
  201. package/dist/esm/payments/index.js.map +1 -1
  202. package/dist/esm/segment-splitter.d.ts.map +1 -1
  203. package/dist/esm/segment-splitter.js +66 -28
  204. package/dist/esm/segment-splitter.js.map +1 -1
  205. package/dist/esm/transactional-email-code.d.ts.map +1 -1
  206. package/dist/esm/transactional-email-code.js +32 -18
  207. package/dist/esm/transactional-email-code.js.map +1 -1
  208. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  209. package/dist/esm/workflow-component-plugin.d.ts.map +1 -1
  210. package/dist/esm/workflow-component-plugin.js +32 -146
  211. package/dist/esm/workflow-component-plugin.js.map +1 -1
  212. package/dist/esm/workflow-project-plugin.d.ts.map +1 -1
  213. package/dist/esm/workflow-project-plugin.js +16 -4
  214. package/dist/esm/workflow-project-plugin.js.map +1 -1
  215. package/dist/esm/workflow-scope.d.ts +19 -0
  216. package/dist/esm/workflow-scope.d.ts.map +1 -0
  217. package/dist/esm/workflow-scope.js +142 -0
  218. package/dist/esm/workflow-scope.js.map +1 -0
  219. package/package.json +4 -4
  220. package/src/account-delete-route-generator.ts +3 -1
  221. package/src/api-route-generator.ts +23 -13
  222. package/src/auth-generator.ts +83 -26
  223. package/src/booking-calendar-feed-route-generator.ts +293 -0
  224. package/src/data-api-route-generator.ts +6 -1
  225. package/src/executor-generator.ts +39 -28
  226. package/src/graph-utils.ts +143 -11
  227. package/src/index.ts +1 -0
  228. package/src/invoice/api-routes-code.ts +166 -21
  229. package/src/invoice/data-access-code.ts +139 -21
  230. package/src/invoice/email-sender-code.ts +15 -25
  231. package/src/invoice/index.ts +1 -1
  232. package/src/invoice/invoice-assembly-code.ts +50 -6
  233. package/src/invoice/invoice-html-code.ts +13 -2
  234. package/src/nodes/data/data-raw-query.ts +11 -1
  235. package/src/nodes/email/email-mailgun.ts +11 -2
  236. package/src/nodes/email/email-postmark.ts +12 -2
  237. package/src/nodes/email/email-resend.ts +12 -2
  238. package/src/nodes/email/email-sendgrid.ts +15 -2
  239. package/src/nodes/general/general-custom-js.ts +10 -0
  240. package/src/nodes/storage/storage-local-remove.ts +4 -0
  241. package/src/nodes/storage/storage-local-set.ts +6 -0
  242. package/src/page-arrival.ts +115 -0
  243. package/src/payments/index.ts +18 -5
  244. package/src/segment-splitter.ts +90 -36
  245. package/src/transactional-email-code.ts +32 -21
  246. package/src/workflow-component-plugin.ts +43 -188
  247. package/src/workflow-project-plugin.ts +17 -4
  248. package/src/workflow-scope.ts +184 -0
@@ -27,7 +27,7 @@ ${generateGetInvoiceByIdCode(dsType, invoicesTable)}
27
27
 
28
28
  ${generateUpdateInvoiceCode(dsType, invoicesTable)}
29
29
 
30
- ${generateStoreInvoicePdfCode(dsType)}
30
+ ${generateStoreInvoicePdfCode(dsType, invoicesTable)}
31
31
 
32
32
  ${generateGetOrderWithItemsCode(dsType)}
33
33
 
@@ -35,6 +35,8 @@ ${generateReplaceInvoiceCode(dsType, invoicesTable, invoiceItemsTable)}
35
35
 
36
36
  ${generateLinkOrderToInvoiceCode(dsType)}
37
37
 
38
+ ${generateRecordOrderEventCode(dsType)}
39
+
38
40
  module.exports = {
39
41
  getClient: getClient,
40
42
  reserveInvoice: reserveInvoice,
@@ -45,6 +47,7 @@ module.exports = {
45
47
  getOrderWithItems: getOrderWithItems,
46
48
  replaceInvoice: replaceInvoice,
47
49
  linkOrderToInvoice: linkOrderToInvoice,
50
+ recordOrderEvent: recordOrderEvent,
48
51
  };
49
52
  `
50
53
  }
@@ -356,18 +359,47 @@ ${cutoff}
356
359
  }`
357
360
  }
358
361
 
359
- // Puts the rendered PDF on the reserved row; `pdf_size_bytes` is what marks
360
- // the invoice finished (see `claimStaleInvoice`).
361
- function generateStoreInvoicePdfCode(dsType: string): string {
362
- const pdfData = dsType === 'supabase' ? `pdfBuffer.toString('base64')` : 'pdfBuffer'
363
- return `
362
+ // Puts the rendered PDF on the reserved row — only while the row has none —
363
+ // and resolves to `{ firstPdf }`: whether this write gave the invoice its
364
+ // first PDF. `pdf_size_bytes` marks the invoice finished (see
365
+ // `claimStaleInvoice`), and the invoice's first PDF is what its email is
366
+ // claimed by: `replaceInvoice` writes first under the same condition, so of a
367
+ // retried payment finishing a stale invoice and a regeneration racing it, one
368
+ // writes the PDF and emails the buyer, and the other is told it was not first.
369
+ function generateStoreInvoicePdfCode(dsType: string, table: string): string {
370
+ if (dsType === 'supabase') {
371
+ return `
364
372
  async function storeInvoicePdf(invoiceId, pdfBuffer, pdfUrl) {
365
- return updateInvoice(invoiceId, {
366
- pdf_data: ${pdfData},
373
+ var result = await getClient().from('${table}').update({
374
+ pdf_data: pdfBuffer.toString('base64'),
367
375
  pdf_size_bytes: pdfBuffer.length,
368
376
  pdf_content_type: 'application/pdf',
369
377
  pdf_url: pdfUrl,
370
- });
378
+ updated_at: new Date().toISOString(),
379
+ }).eq('id', invoiceId).is('pdf_size_bytes', null).select('id');
380
+ if (result.error) throw new Error(result.error.message);
381
+ return { firstPdf: !!(result.data && result.data.length > 0) };
382
+ }`
383
+ }
384
+
385
+ if (dsType === 'mysql' || dsType === 'mariadb' || dsType === 'tidb') {
386
+ return `
387
+ async function storeInvoicePdf(invoiceId, pdfBuffer, pdfUrl) {
388
+ var [result] = await getClient().execute(
389
+ 'UPDATE ${table} SET pdf_data = ?, pdf_size_bytes = ?, pdf_content_type = ?, pdf_url = ?, updated_at = ? WHERE id = ? AND pdf_size_bytes IS NULL',
390
+ [pdfBuffer, pdfBuffer.length, 'application/pdf', pdfUrl, new Date().toISOString(), invoiceId]
391
+ );
392
+ return { firstPdf: !!(result && result.affectedRows > 0) };
393
+ }`
394
+ }
395
+
396
+ return `
397
+ async function storeInvoicePdf(invoiceId, pdfBuffer, pdfUrl) {
398
+ var result = await getClient().query(
399
+ 'UPDATE ${table} SET pdf_data = $1, pdf_size_bytes = $2, pdf_content_type = $3, pdf_url = $4, updated_at = $5 WHERE id = $6 AND pdf_size_bytes IS NULL',
400
+ [pdfBuffer, pdfBuffer.length, 'application/pdf', pdfUrl, new Date().toISOString(), invoiceId]
401
+ );
402
+ return { firstPdf: result.rowCount > 0 };
371
403
  }`
372
404
  }
373
405
 
@@ -565,6 +597,15 @@ async function getOrderWithItems(orderId) {
565
597
  // points at it (the order, the sent-email ledger) stays valid. Postgres and
566
598
  // MySQL do it in one transaction, and Supabase undoes its steps, so a failure
567
599
  // leaves the old invoice whole.
600
+ //
601
+ // Resolves to `{ firstPdf }`: whether this write gave the invoice its first
602
+ // PDF — one whose PDF never came at payment, so its buyer was never emailed
603
+ // it. The database decides: the row is first written only where it has no PDF
604
+ // (the `pdf_size_bytes IS NULL` that `claimStaleInvoice` reads as
605
+ // "unfinished"), and of two regenerations racing for the same invoice the
606
+ // second waits for the first's row lock, then finds the PDF and matches
607
+ // nothing. `storeInvoicePdf` writes the payment's PDF under the same
608
+ // condition, so exactly one write is ever told `firstPdf: true`.
568
609
  function generateReplaceInvoiceCode(dsType: string, table: string, itemsTable: string): string {
569
610
  if (dsType === 'supabase') {
570
611
  return `
@@ -584,18 +625,23 @@ async function replaceInvoice(invoiceId, invoiceData, items) {
584
625
  var insertedIds = (inserted || []).map(function (row) { return row.id; });
585
626
  var written = null;
586
627
  try {
587
- var record = await writeInvoiceRecord(mapInvoiceReplacementToRecord(invoiceData), async function (fields) {
588
- var result = await client.from('${table}').update(fields).eq('id', invoiceId).select('id');
589
- if (result.error) throw new Error(result.error.message);
590
- if (!result.data || result.data.length === 0) throw new Error('Invoice ' + invoiceId + ' no longer exists');
628
+ var outcome = await writeInvoiceRecord(mapInvoiceReplacementToRecord(invoiceData), async function (fields) {
629
+ var first = await client.from('${table}').update(fields).eq('id', invoiceId).is('pdf_size_bytes', null).select('id');
630
+ if (first.error) throw new Error(first.error.message);
631
+ var firstPdf = !!(first.data && first.data.length > 0);
632
+ if (!firstPdf) {
633
+ var result = await client.from('${table}').update(fields).eq('id', invoiceId).select('id');
634
+ if (result.error) throw new Error(result.error.message);
635
+ if (!result.data || result.data.length === 0) throw new Error('Invoice ' + invoiceId + ' no longer exists');
636
+ }
591
637
  written = fields;
592
- return fields;
638
+ return { firstPdf: firstPdf };
593
639
  });
594
640
  if (previousIds.length > 0) {
595
641
  var removed = await client.from('${itemsTable}').delete().in('id', previousIds);
596
642
  if (removed.error) throw new Error(removed.error.message);
597
643
  }
598
- return record;
644
+ return outcome;
599
645
  } catch (err) {
600
646
  if (written) {
601
647
  var restored = {};
@@ -624,12 +670,17 @@ async function replaceInvoice(invoiceId, invoiceData, items) {
624
670
  var setClauses = columns.map(function(k) { return quoteIdent(k) + ' = ?'; }).join(', ');
625
671
  var values = columns.map(function(k) { return fields[k]; });
626
672
  values.push(invoiceId);
627
- var [updated] = await conn.execute('UPDATE ${table} SET ' + setClauses + ' WHERE id = ?', values);
628
- if (!updated || !updated.affectedRows) throw new Error('Invoice ' + invoiceId + ' no longer exists');
673
+ var update = 'UPDATE ${table} SET ' + setClauses + ' WHERE id = ?';
674
+ var [first] = await conn.execute(update + ' AND pdf_size_bytes IS NULL', values);
675
+ var firstPdf = !!(first && first.affectedRows > 0);
676
+ if (!firstPdf) {
677
+ var [updated] = await conn.execute(update, values);
678
+ if (!updated || !updated.affectedRows) throw new Error('Invoice ' + invoiceId + ' no longer exists');
679
+ }
629
680
  await conn.execute('DELETE FROM ${itemsTable} WHERE invoice_id = ?', [invoiceId]);
630
681
  await insertInvoiceItemRows(conn, invoiceId, items);
631
682
  await conn.commit();
632
- return fields;
683
+ return { firstPdf: firstPdf };
633
684
  } catch (err) {
634
685
  try { await conn.rollback(); } catch (_rollbackErr) {}
635
686
  throw err;
@@ -653,12 +704,16 @@ async function replaceInvoice(invoiceId, invoiceData, items) {
653
704
  values.push(invoiceId);
654
705
  // The UPDATE takes the row lock first, so two regenerations of the same
655
706
  // invoice run one after the other instead of interleaving their lines.
656
- var updated = await client.query('UPDATE ${table} SET ' + setClauses + ' WHERE id = $' + values.length, values);
657
- if (!updated.rowCount) throw new Error('Invoice ' + invoiceId + ' no longer exists');
707
+ var update = 'UPDATE ${table} SET ' + setClauses + ' WHERE id = $' + values.length;
708
+ var firstPdf = (await client.query(update + ' AND pdf_size_bytes IS NULL', values)).rowCount > 0;
709
+ if (!firstPdf) {
710
+ var updated = await client.query(update, values);
711
+ if (!updated.rowCount) throw new Error('Invoice ' + invoiceId + ' no longer exists');
712
+ }
658
713
  await client.query('DELETE FROM ${itemsTable} WHERE invoice_id = $1', [invoiceId]);
659
714
  await insertInvoiceItemRows(client, invoiceId, items);
660
715
  await client.query('COMMIT');
661
- return fields;
716
+ return { firstPdf: firstPdf };
662
717
  } catch (err) {
663
718
  try { await client.query('ROLLBACK'); } catch (_rollbackErr) {}
664
719
  throw err;
@@ -719,6 +774,69 @@ async function linkOrderToInvoice(orderId, invoiceId, invoiceNumber, pdfUrl) {
719
774
  }`
720
775
  }
721
776
 
777
+ // One line of an order's history: a row of the activity log the editor
778
+ // provisions (`teleport_events`) and both order timelines read. Written like
779
+ // every line the store's workflows write — teleport-gui's
780
+ // `constants/order-event-types.ts` owns the vocabulary — so a replay collides
781
+ // on the UNIQUE `dedupe_key` and adds nothing. `event` is
782
+ // `{ type, dedupeKey, orderId, payload, actor, actorType }`.
783
+ function generateRecordOrderEventCode(dsType: string): string {
784
+ const row = `
785
+ var ORDER_EVENTS_TABLE = 'teleport_events';
786
+ var ORDER_EVENT_DEDUPE_KEY_MAX_LENGTH = 255;
787
+
788
+ function orderEventRow(event) {
789
+ return {
790
+ id: require('crypto').randomUUID(),
791
+ event_type: event.type,
792
+ dedupe_key: String(event.dedupeKey).slice(0, ORDER_EVENT_DEDUPE_KEY_MAX_LENGTH),
793
+ order_id: event.orderId || null,
794
+ payload: JSON.stringify(event.payload || {}),
795
+ actor: event.actor || '',
796
+ actor_type: event.actorType,
797
+ // Logged; a dispatcher claims the rows it delivers itself.
798
+ status: 'recorded',
799
+ };
800
+ }`
801
+
802
+ if (dsType === 'supabase') {
803
+ return `${row}
804
+
805
+ async function recordOrderEvent(event) {
806
+ var result = await getClient()
807
+ .from(ORDER_EVENTS_TABLE)
808
+ .upsert(orderEventRow(event), { onConflict: 'dedupe_key', ignoreDuplicates: true });
809
+ if (result.error) throw new Error(result.error.message);
810
+ }`
811
+ }
812
+
813
+ if (dsType === 'mysql' || dsType === 'mariadb' || dsType === 'tidb') {
814
+ return `${row}
815
+
816
+ async function recordOrderEvent(event) {
817
+ var record = orderEventRow(event);
818
+ var columns = Object.keys(record);
819
+ var placeholders = columns.map(function() { return '?'; }).join(', ');
820
+ await getClient().execute(
821
+ 'INSERT INTO ' + ORDER_EVENTS_TABLE + ' (' + columns.join(', ') + ') VALUES (' + placeholders + ') ON DUPLICATE KEY UPDATE dedupe_key = dedupe_key',
822
+ columns.map(function(k) { return record[k]; })
823
+ );
824
+ }`
825
+ }
826
+
827
+ return `${row}
828
+
829
+ async function recordOrderEvent(event) {
830
+ var record = orderEventRow(event);
831
+ var columns = Object.keys(record);
832
+ var placeholders = columns.map(function(_, i) { return '$' + (i + 1); }).join(', ');
833
+ await getClient().query(
834
+ 'INSERT INTO ' + ORDER_EVENTS_TABLE + ' (' + columns.join(', ') + ') VALUES (' + placeholders + ') ON CONFLICT (dedupe_key) DO NOTHING',
835
+ columns.map(function(k) { return record[k]; })
836
+ );
837
+ }`
838
+ }
839
+
722
840
  export const getRecordMappingCode = (dsType?: string): string => `
723
841
  function mapInvoiceToRecord(data) {
724
842
  return {
@@ -1,4 +1,5 @@
1
1
  import { UIDLInvoiceEmailDelivery } from '@teleporthq/teleport-types'
2
+ import { EmailSender } from '@teleporthq/teleport-shared'
2
3
 
3
4
  export const generateEmailSenderCode = (emailDelivery: UIDLInvoiceEmailDelivery): string => {
4
5
  if (!emailDelivery || !emailDelivery.enabled || !emailDelivery.provider) {
@@ -33,6 +34,8 @@ var BODY_TEMPLATE = ${JSON.stringify(bodyTemplate)};
33
34
  // Per-language copies of the subject/body (main language excluded), chosen by
34
35
  // the locale the generate route resolved for the invoice (the order's).
35
36
  var LOCALIZED_TEMPLATES = ${JSON.stringify(localizedTemplates)};
37
+ ${EmailSender.generateEmailSenderHelperCode()}
38
+ var SENDER = __emailSender(${JSON.stringify({ from: fromEmail, name: fromName })});
36
39
 
37
40
  function resolveSecretKey(keyName) {
38
41
  var envName = SECRET_KEYS[keyName];
@@ -52,7 +55,8 @@ function buildEmailData(invoiceData) {
52
55
  subject: replacePlaceholders(copy.subject, data),
53
56
  body: replacePlaceholders(copy.body, data),
54
57
  to: invoiceData.customerEmail || '',
55
- from: ${JSON.stringify(fromName ? `${fromName} <${fromEmail}>` : fromEmail)},
58
+ from: SENDER.header,
59
+ sender: SENDER,
56
60
  templateData: data,
57
61
  };
58
62
  }
@@ -63,7 +67,9 @@ function attachmentName(invoiceData) {
63
67
 
64
68
  ${generateProviderSendFunction(emailDelivery.provider)}
65
69
 
66
- async function sendInvoiceEmail(invoiceData, pdfBuffer) {
70
+ // sourceRef names the route that sent it in the ledger: the payment's
71
+ // generate route unless the caller says otherwise.
72
+ async function sendInvoiceEmail(invoiceData, pdfBuffer, sourceRef) {
67
73
  var emailData = buildEmailData(invoiceData);
68
74
  if (!emailData.to) return { success: false, error: 'No customer email provided' };
69
75
  var result;
@@ -77,6 +83,7 @@ async function sendInvoiceEmail(invoiceData, pdfBuffer) {
77
83
  audience: 'customer',
78
84
  to: emailData.to,
79
85
  from: emailData.from,
86
+ fromName: emailData.sender.name,
80
87
  subject: emailData.subject,
81
88
  html: emailData.body,
82
89
  payload: emailData.templateData,
@@ -91,7 +98,7 @@ async function sendInvoiceEmail(invoiceData, pdfBuffer) {
91
98
  status: result && result.success ? 'sent' : 'failed',
92
99
  error: result && !result.success ? result.error : null,
93
100
  source: 'invoice',
94
- sourceRef: 'api/invoices/generate',
101
+ sourceRef: sourceRef || 'api/invoices/generate',
95
102
  orderId: invoiceData.orderId,
96
103
  referenceType: 'invoice',
97
104
  referenceId: invoiceData.id,
@@ -139,9 +146,12 @@ async function sendInvoiceEmailWithProvider(emailData, invoiceData, pdfBuffer) {
139
146
  var apiKey = resolveSecretKey('apiKey');
140
147
  if (!apiKey) return { success: false, error: 'SendGrid API key not configured' };
141
148
  sgMail.setApiKey(apiKey);
149
+ // SendGrid reads a "Name <email>" string's name verbatim, quotes included,
150
+ // so a named sender goes as its two parts.
151
+ var sender = emailData.sender;
142
152
  var msg = {
143
153
  to: emailData.to,
144
- from: emailData.from,
154
+ from: sender.name ? { email: sender.email, name: sender.name } : sender.email,
145
155
  subject: emailData.subject,
146
156
  html: emailData.body,
147
157
  attachments: [{
@@ -217,26 +227,6 @@ async function sendInvoiceEmailWithProvider(emailData, invoiceData, pdfBuffer) {
217
227
 
218
228
  case 'mailersend':
219
229
  return `
220
- // Pulls "name" and "email" out of the RFC-5322-ish \`from\` string the
221
- // builder assembles. Three shapes in the wild:
222
- // 1. "Acme Billing <billing@acme.com>" → name="Acme Billing", email="billing@acme.com"
223
- // 2. "billing@acme.com" → name="", email="billing@acme.com"
224
- // 3. "" → {}, caller handles the fallback
225
- // The old implementation used naive \`.replace()\` chains that produced
226
- // \`name="billing@acme.com"\` in case (2) — MailerSend accepts that but the
227
- // email then appears in the recipient's client with itself as the sender
228
- // name, which is ugly. Kept as a pure helper (no fallbacks baked in) so
229
- // the caller decides what to do when a field is missing.
230
- function parseFromString(from) {
231
- var str = String(from || '').trim();
232
- if (!str) return { name: '', email: '' };
233
- var m = /^(.*)<([^<>]+)>\\s*$/.exec(str);
234
- if (m) {
235
- return { name: m[1].trim(), email: m[2].trim() };
236
- }
237
- return { name: '', email: str };
238
- }
239
-
240
230
  async function sendInvoiceEmailWithProvider(emailData, invoiceData, pdfBuffer) {
241
231
  var invoiceNumber = invoiceData && invoiceData.invoiceNumber ? invoiceData.invoiceNumber : '(no number)';
242
232
  try {
@@ -256,7 +246,7 @@ async function sendInvoiceEmailWithProvider(emailData, invoiceData, pdfBuffer) {
256
246
 
257
247
  var mailerSend = new MailerSend({ apiKey: apiKey });
258
248
 
259
- var parsedFrom = parseFromString(emailData.from);
249
+ var parsedFrom = emailData.sender;
260
250
  if (!parsedFrom.email) {
261
251
  console.error('[invoice-email][mailersend] skip — from address is empty (set invoiceSettings.emailDelivery.fromEmail)');
262
252
  return { success: false, error: 'Sender email not configured' };
@@ -157,7 +157,7 @@ export const generateInvoiceFiles = (
157
157
  {
158
158
  name: 'regenerate',
159
159
  fileType: FileType.JS,
160
- content: generateInvoiceRegenerateRouteCode(),
160
+ content: generateInvoiceRegenerateRouteCode(invoiceSettings),
161
161
  },
162
162
  ],
163
163
  })
@@ -33,6 +33,36 @@ var TEMPLATE_DOCUMENT = ${templateDocumentJson};
33
33
  ${REGIONAL_INVOICE_TAX_CODE}
34
34
  ${ProductOptions.generateProductOptionsHelperCode()}
35
35
 
36
+ // The invoice details an order's metadata (JSON text) carries beside its
37
+ // billing columns — the keys \`ORDER_BILLING_METADATA_KEYS\` (teleport-gui)
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.
41
+ function readOrderBillingMetadata(raw) {
42
+ var parsed = raw;
43
+ if (typeof raw === 'string') {
44
+ try {
45
+ parsed = JSON.parse(raw);
46
+ } catch (e) {
47
+ parsed = null;
48
+ }
49
+ }
50
+ var meta = parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
51
+ function part(key) {
52
+ var value = meta[key];
53
+ return value === null || value === undefined ? '' : String(value).trim();
54
+ }
55
+ return {
56
+ taxId: part('billingTaxId'),
57
+ company: part('billingCompany'),
58
+ street: part('billingStreet'),
59
+ city: part('billingCity'),
60
+ postalCode: part('billingPostalCode'),
61
+ region: part('billingRegion'),
62
+ country: part('billingCountry'),
63
+ };
64
+ }
65
+
36
66
  // The product options an order line was bought with: the short one-line label
37
67
  // (after the name) and every answer in full, one per line (the description).
38
68
  // Same rule as the order emails: a current snapshot names its options, while
@@ -416,15 +446,28 @@ function assembleInvoiceData(body, hydratedOrder, hydratedItems) {
416
446
  // field but the order has one, use it. Kept permissive on field
417
447
  // names (billing_* wins, falls back to shipping_*) because a buyer
418
448
  // who checked "bill to a different address" has both on the order.
449
+ //
450
+ // An order whose buyer gave invoice details of their own (a booking'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.
419
456
  var orderRow = orderShippingSource;
457
+ var orderBilling = readOrderBillingMetadata(orderRow.metadata);
458
+ var billedApart = !!(orderBilling.street || orderBilling.city || orderBilling.country);
420
459
  var fallbackCustomerName =
421
460
  orderRow.billing_name || orderRow.shipping_name || orderRow.customer_name || '';
422
461
  var fallbackCustomerEmail = orderRow.billing_email || orderRow.customer_email || '';
423
- var fallbackCustomerAddress = orderRow.billing_address || orderRow.shipping_address || '';
424
- var fallbackCustomerCity = orderRow.shipping_city || '';
425
- var fallbackCustomerState = orderRow.shipping_state || '';
426
- var fallbackCustomerZip = orderRow.shipping_zip || '';
427
- var fallbackCustomerCountry = orderRow.shipping_country || '';
462
+ var fallbackCustomerAddress = billedApart
463
+ ? orderBilling.street
464
+ : orderRow.billing_address || orderRow.shipping_address || '';
465
+ var fallbackCustomerCity = billedApart ? orderBilling.city : orderRow.shipping_city || '';
466
+ var fallbackCustomerState = billedApart ? orderBilling.region : orderRow.shipping_state || '';
467
+ var fallbackCustomerZip = billedApart ? orderBilling.postalCode : orderRow.shipping_zip || '';
468
+ var fallbackCustomerCountry = billedApart
469
+ ? orderBilling.country
470
+ : orderRow.shipping_country || '';
428
471
  var fallbackPaymentMethod = orderRow.payment_method || '';
429
472
  var fallbackPaymentProvider = orderRow.payment_provider || '';
430
473
  var fallbackPaymentIntentId = orderRow.payment_intent_id || '';
@@ -446,7 +489,8 @@ function assembleInvoiceData(body, hydratedOrder, hydratedItems) {
446
489
  customerState: body.customerState || fallbackCustomerState || '',
447
490
  customerZip: body.customerZip || fallbackCustomerZip || '',
448
491
  customerCountry: body.customerCountry || fallbackCustomerCountry || '',
449
- customerVat: body.customerVat || '',
492
+ customerVat: body.customerVat || orderBilling.taxId || '',
493
+ customerCompany: body.customerCompany || orderBilling.company || '',
450
494
  companyName: pdfGenerator.COMPANY_DETAILS.companyName || '',
451
495
  companyAddress: pdfGenerator.COMPANY_DETAILS.companyAddress || '',
452
496
  companyCity: pdfGenerator.COMPANY_DETAILS.companyCity || '',
@@ -355,9 +355,13 @@ function buildInvoiceDataScope(invoiceData) {
355
355
  // sample — right for a company address, wrong for a code or a card tail: an
356
356
  // order without a voucher must not print the designer's "SAVE20".
357
357
  var realInvoiceNeverSampled = { voucherCode: voucherCode, giftCardLast4: giftCardLast4 };
358
+ // The buyer and their payment are never sampled either: an order with no
359
+ // city (a booking, a pickup, a download) must not print the designer's
360
+ // sample customer's, nor a card it was not paid with.
358
361
 
359
362
  var realCustomer = {
360
363
  name: invoiceData.customerName || '',
364
+ company: invoiceData.customerCompany || '',
361
365
  email: invoiceData.customerEmail || '',
362
366
  address: invoiceData.customerAddress || '',
363
367
  city: invoiceData.customerCity || '',
@@ -366,6 +370,12 @@ function buildInvoiceDataScope(invoiceData) {
366
370
  country: invoiceData.customerCountry || '',
367
371
  vat: invoiceData.customerVat || '',
368
372
  };
373
+ // The "Bill to" lines a buyer may leave out show only when they did not
374
+ // (string flags: the template compares strings, as with \`hasShipping\`).
375
+ realCustomer.hasCompany = realCustomer.company ? 'true' : 'false';
376
+ realCustomer.hasZip = realCustomer.zip ? 'true' : 'false';
377
+ realCustomer.hasCountry = realCustomer.country ? 'true' : 'false';
378
+ realCustomer.hasVat = realCustomer.vat ? 'true' : 'false';
369
379
 
370
380
  var realPayment = {
371
381
  method: invoiceData.paymentMethod || '',
@@ -462,8 +472,8 @@ function buildInvoiceDataScope(invoiceData) {
462
472
  invoiceData: {
463
473
  Company: mergeWithDefaults(realCompany, defaults.Company),
464
474
  Invoice: Object.assign(mergeWithDefaults(realInvoice, defaults.Invoice), realInvoiceNeverSampled),
465
- Customer: mergeWithDefaults(realCustomer, defaults.Customer),
466
- Payment: mergeWithDefaults(realPayment, defaults.Payment),
475
+ Customer: realCustomer,
476
+ Payment: realPayment,
467
477
  Products: realProducts.length > 0 ? realProducts : (defaults.Products || []),
468
478
  },
469
479
  };
@@ -972,6 +982,7 @@ function buildFallbackInvoiceHtml(scope) {
972
982
  escapeHtml(comp.city || '') + '<br/>' +
973
983
  escapeHtml(comp.email || '') + '</div>');
974
984
  parts.push('<div style="flex:1;"><strong>' + escapeHtml(cust.name || '') + '</strong><br/>' +
985
+ (cust.company ? escapeHtml(cust.company) + '<br/>' : '') +
975
986
  escapeHtml(cust.email || '') + '<br/>' +
976
987
  escapeHtml(cust.address || '') + '<br/>' +
977
988
  escapeHtml(cust.city || '') + '</div>');
@@ -36,7 +36,17 @@ async function data_raw_query(config: any, context: any) {
36
36
  const data = await response.json()
37
37
 
38
38
  if (!response.ok) {
39
- return { rows: [], result: [], error: data.error || 'Raw query failed' }
39
+ const failure: Record<string, unknown> = {
40
+ rows: [],
41
+ result: [],
42
+ error: data.error || 'Raw query failed',
43
+ }
44
+ // The database's SQLSTATE (the route keeps its message in the server log): a
45
+ // statement that raises on purpose is told apart from a broken one by it.
46
+ if (data.code) {
47
+ failure.code = data.code
48
+ }
49
+ return failure
40
50
  }
41
51
  const rows = data.rows || []
42
52
  return { rows, result: rows }
@@ -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 = fromName ? fromName + ' <' + from + '>' : from
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 handlerToString(email_mailgun)
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 handlerToString(email_postmark)
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 handlerToString(email_resend)
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 handlerToString(email_sendgrid)
75
+ return assertHandlerHasNoModuleRefs(
76
+ handlerToString(email_sendgrid) + '\n' + EmailSender.generateEmailSenderHelperCode(),
77
+ 'email-sendgrid'
78
+ )
66
79
  },
67
80
  }
@@ -194,6 +194,16 @@ async function general_custom_js(config: any, context: Record<string, unknown>)
194
194
  Object.keys(resolvedNamedParams).length > 0
195
195
  ) {
196
196
  list.push(resolvedNamedParams)
197
+ // The named inputs are also readable as `params.<name>`, the way the
198
+ // custom-node path overlays `__customParams`: generated functions
199
+ // reach for `params.activeStep` as often as `params.slice(-1)[0]`, and
200
+ // an undefined there silently saved `{}` to storage.
201
+ const namedKeys = Object.keys(resolvedNamedParams)
202
+ for (let nk = 0; nk < namedKeys.length; nk++) {
203
+ if (!(namedKeys[nk] in list)) {
204
+ ;(list as any)[namedKeys[nk]] = resolvedNamedParams[namedKeys[nk]]
205
+ }
206
+ }
197
207
  }
198
208
  params = list
199
209
  }