@teleporthq/teleport-plugin-next-workflows 0.44.8 → 0.44.9

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 (117) hide show
  1. package/__tests__/_helpers/load-server-runtime.ts +6 -2
  2. package/__tests__/_helpers/run-account-delete-route.ts +140 -0
  3. package/__tests__/account-delete-current-password.test.ts +73 -0
  4. package/__tests__/account-delete-password-check.test.ts +113 -0
  5. package/__tests__/account-delete-visitor-details.test.ts +206 -0
  6. package/__tests__/auth-password-change-ends-sessions.test.ts +178 -0
  7. package/__tests__/data-api-invalid-body.test.ts +110 -0
  8. package/__tests__/data-node-not-awaited.test.ts +7 -6
  9. package/__tests__/invoice-payment-line.test.ts +68 -0
  10. package/__tests__/invoice-tender.test.ts +116 -0
  11. package/__tests__/pending-work-after-reply.test.ts +294 -0
  12. package/__tests__/runtime-condition-operands.test.ts +86 -0
  13. package/__tests__/webhook-delivery-lease.test.ts +68 -6
  14. package/dist/cjs/account-delete-route-generator.d.ts +7 -2
  15. package/dist/cjs/account-delete-route-generator.d.ts.map +1 -1
  16. package/dist/cjs/account-delete-route-generator.js +90 -43
  17. package/dist/cjs/account-delete-route-generator.js.map +1 -1
  18. package/dist/cjs/api-route-generator.d.ts.map +1 -1
  19. package/dist/cjs/api-route-generator.js +12 -13
  20. package/dist/cjs/api-route-generator.js.map +1 -1
  21. package/dist/cjs/auth-generator.d.ts.map +1 -1
  22. package/dist/cjs/auth-generator.js +34 -1
  23. package/dist/cjs/auth-generator.js.map +1 -1
  24. package/dist/cjs/await-result.d.ts +13 -5
  25. package/dist/cjs/await-result.d.ts.map +1 -1
  26. package/dist/cjs/await-result.js +19 -8
  27. package/dist/cjs/await-result.js.map +1 -1
  28. package/dist/cjs/data-api-route-generator.d.ts.map +1 -1
  29. package/dist/cjs/data-api-route-generator.js +9 -1
  30. package/dist/cjs/data-api-route-generator.js.map +1 -1
  31. package/dist/cjs/executor-generator.d.ts.map +1 -1
  32. package/dist/cjs/executor-generator.js +18 -6
  33. package/dist/cjs/executor-generator.js.map +1 -1
  34. package/dist/cjs/invoice/invoice-html-code.d.ts.map +1 -1
  35. package/dist/cjs/invoice/invoice-html-code.js +96 -5
  36. package/dist/cjs/invoice/invoice-html-code.js.map +1 -1
  37. package/dist/cjs/nodes/account/account-delete-current.d.ts.map +1 -1
  38. package/dist/cjs/nodes/account/account-delete-current.js +4 -1
  39. package/dist/cjs/nodes/account/account-delete-current.js.map +1 -1
  40. package/dist/cjs/server-runtime-code.d.ts.map +1 -1
  41. package/dist/cjs/server-runtime-code.js +21 -0
  42. package/dist/cjs/server-runtime-code.js.map +1 -1
  43. package/dist/cjs/transactional-email-code.d.ts +7 -0
  44. package/dist/cjs/transactional-email-code.d.ts.map +1 -1
  45. package/dist/cjs/transactional-email-code.js +5 -5
  46. package/dist/cjs/transactional-email-code.js.map +1 -1
  47. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  48. package/dist/cjs/vercel-functions.d.ts +34 -0
  49. package/dist/cjs/vercel-functions.d.ts.map +1 -0
  50. package/dist/cjs/vercel-functions.js +62 -0
  51. package/dist/cjs/vercel-functions.js.map +1 -0
  52. package/dist/cjs/webhook-delivery-lease/webhook-delivery-lease-code.d.ts +6 -0
  53. package/dist/cjs/webhook-delivery-lease/webhook-delivery-lease-code.d.ts.map +1 -1
  54. package/dist/cjs/webhook-delivery-lease/webhook-delivery-lease-code.js +26 -11
  55. package/dist/cjs/webhook-delivery-lease/webhook-delivery-lease-code.js.map +1 -1
  56. package/dist/cjs/workflow-project-plugin.d.ts.map +1 -1
  57. package/dist/cjs/workflow-project-plugin.js +3 -0
  58. package/dist/cjs/workflow-project-plugin.js.map +1 -1
  59. package/dist/esm/account-delete-route-generator.d.ts +7 -2
  60. package/dist/esm/account-delete-route-generator.d.ts.map +1 -1
  61. package/dist/esm/account-delete-route-generator.js +90 -43
  62. package/dist/esm/account-delete-route-generator.js.map +1 -1
  63. package/dist/esm/api-route-generator.d.ts.map +1 -1
  64. package/dist/esm/api-route-generator.js +12 -13
  65. package/dist/esm/api-route-generator.js.map +1 -1
  66. package/dist/esm/auth-generator.d.ts.map +1 -1
  67. package/dist/esm/auth-generator.js +34 -1
  68. package/dist/esm/auth-generator.js.map +1 -1
  69. package/dist/esm/await-result.d.ts +13 -5
  70. package/dist/esm/await-result.d.ts.map +1 -1
  71. package/dist/esm/await-result.js +18 -7
  72. package/dist/esm/await-result.js.map +1 -1
  73. package/dist/esm/data-api-route-generator.d.ts.map +1 -1
  74. package/dist/esm/data-api-route-generator.js +9 -1
  75. package/dist/esm/data-api-route-generator.js.map +1 -1
  76. package/dist/esm/executor-generator.d.ts.map +1 -1
  77. package/dist/esm/executor-generator.js +18 -6
  78. package/dist/esm/executor-generator.js.map +1 -1
  79. package/dist/esm/invoice/invoice-html-code.d.ts.map +1 -1
  80. package/dist/esm/invoice/invoice-html-code.js +96 -5
  81. package/dist/esm/invoice/invoice-html-code.js.map +1 -1
  82. package/dist/esm/nodes/account/account-delete-current.d.ts.map +1 -1
  83. package/dist/esm/nodes/account/account-delete-current.js +4 -1
  84. package/dist/esm/nodes/account/account-delete-current.js.map +1 -1
  85. package/dist/esm/server-runtime-code.d.ts.map +1 -1
  86. package/dist/esm/server-runtime-code.js +21 -0
  87. package/dist/esm/server-runtime-code.js.map +1 -1
  88. package/dist/esm/transactional-email-code.d.ts +7 -0
  89. package/dist/esm/transactional-email-code.d.ts.map +1 -1
  90. package/dist/esm/transactional-email-code.js +5 -5
  91. package/dist/esm/transactional-email-code.js.map +1 -1
  92. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  93. package/dist/esm/vercel-functions.d.ts +34 -0
  94. package/dist/esm/vercel-functions.d.ts.map +1 -0
  95. package/dist/esm/vercel-functions.js +57 -0
  96. package/dist/esm/vercel-functions.js.map +1 -0
  97. package/dist/esm/webhook-delivery-lease/webhook-delivery-lease-code.d.ts +6 -0
  98. package/dist/esm/webhook-delivery-lease/webhook-delivery-lease-code.d.ts.map +1 -1
  99. package/dist/esm/webhook-delivery-lease/webhook-delivery-lease-code.js +26 -11
  100. package/dist/esm/webhook-delivery-lease/webhook-delivery-lease-code.js.map +1 -1
  101. package/dist/esm/workflow-project-plugin.d.ts.map +1 -1
  102. package/dist/esm/workflow-project-plugin.js +3 -0
  103. package/dist/esm/workflow-project-plugin.js.map +1 -1
  104. package/package.json +2 -2
  105. package/src/account-delete-route-generator.ts +97 -45
  106. package/src/api-route-generator.ts +12 -13
  107. package/src/auth-generator.ts +34 -1
  108. package/src/await-result.ts +19 -7
  109. package/src/data-api-route-generator.ts +9 -1
  110. package/src/executor-generator.ts +18 -6
  111. package/src/invoice/invoice-html-code.ts +96 -5
  112. package/src/nodes/account/account-delete-current.ts +4 -1
  113. package/src/server-runtime-code.ts +22 -0
  114. package/src/transactional-email-code.ts +12 -5
  115. package/src/vercel-functions.ts +67 -0
  116. package/src/webhook-delivery-lease/webhook-delivery-lease-code.ts +26 -11
  117. package/src/workflow-project-plugin.ts +3 -0
@@ -1,3 +1,5 @@
1
+ import { generateVercelFunctionsLoaderCode } from './vercel-functions'
2
+
1
3
  /**
2
4
  * `utils/workflows/server-runtime.js`: what every server route loads. It is the
3
5
  * shared runtime utilities plus the parts only a server may run — the signed
@@ -42,6 +44,8 @@ export const generateServerRuntimeCode = (): string => `'use strict';
42
44
  var crypto = require('crypto');
43
45
  var utils = require('./runtime-utils');
44
46
 
47
+ ${generateVercelFunctionsLoaderCode()}
48
+
45
49
  var __stepKeyCache = { secret: null, key: null };
46
50
 
47
51
  function stepKey() {
@@ -394,7 +398,25 @@ function trustedBaseUrl(req) {
394
398
  return origin;
395
399
  }
396
400
 
401
+ // The work a segment's not-awaited steps left running (an invoice being
402
+ // rendered and mailed) must still finish, but the visitor need not wait for
403
+ // it: on Vercel it is handed to waitUntil, which keeps the function running
404
+ // after the reply. Anywhere else the route awaits it before replying, since a
405
+ // function frozen after its reply would drop it.
406
+ function handOffPendingNodePromises(context) {
407
+ var queue = context && context.__pendingNodePromises;
408
+ if (!queue || queue.length === 0) return Promise.resolve();
409
+ // Drained through the queue itself: the route deletes it from the context
410
+ // before replying, and a parallel branch still running pushes onto it.
411
+ var drained = utils.settlePendingNodePromises({ __pendingNodePromises: queue });
412
+ var waitUntil = __vercelWaitUntil();
413
+ if (!waitUntil) return drained;
414
+ waitUntil(drained);
415
+ return Promise.resolve();
416
+ }
417
+
397
418
  module.exports = Object.assign({}, utils, {
419
+ handOffPendingNodePromises: handOffPendingNodePromises,
398
420
  claimSegmentContext: claimSegmentContext,
399
421
  segmentReply: segmentReply,
400
422
  internalRequestHeaders: internalRequestHeaders,
@@ -38,10 +38,17 @@ export interface TransactionalEmailLedger {
38
38
  source: string
39
39
  sourceRef: string
40
40
  relativePrefix: string
41
+ /**
42
+ * Records the send without the recipient's details: `msg.recordedTo` in
43
+ * place of the address, and neither the body nor the values it was filled
44
+ * with. For an email to someone whose details the route is removing (the
45
+ * farewell after an account deletion).
46
+ */
47
+ withoutRecipientDetails?: boolean
41
48
  }
42
49
 
43
- // Provider-specific `__sendProviderEmail({ from, fromName?, to, subject, html,
44
- // apiKey, tokenValues?, userId? })`, preceded by the `__emailSender` helper that
50
+ // Provider-specific `__sendProviderEmail({ from, fromName?, to, recordedTo?,
51
+ // subject, html, apiKey, tokenValues?, userId? })`, preceded by the `__emailSender` helper that
45
52
  // names the sender. No attachment (unlike the invoice sender). When no provider
46
53
  // is configured the function is a no-op so the route stays self-contained and
47
54
  // never throws. With `ledger`, the provider function is emitted as
@@ -77,12 +84,12 @@ async function __sendProviderEmail(msg) {
77
84
  __sentEmailLog.recordSentEmail({
78
85
  emailType: ${JSON.stringify(ledger.emailType)},
79
86
  audience: 'customer',
80
- to: msg.to,
87
+ to: ${ledger.withoutRecipientDetails ? 'msg.recordedTo' : 'msg.to'},
81
88
  from: sender.email,
82
89
  fromName: sender.name,
83
90
  subject: msg.subject,
84
- html: msg.html,
85
- payload: msg.tokenValues,
91
+ html: ${ledger.withoutRecipientDetails ? "''" : 'msg.html'},
92
+ payload: ${ledger.withoutRecipientDetails ? 'null' : 'msg.tokenValues'},
86
93
  provider: ${JSON.stringify(provider)},
87
94
  status: failure ? 'failed' : 'sent',
88
95
  error: failure ? (failure.message || String(failure)) : null,
@@ -0,0 +1,67 @@
1
+ /**
2
+ * `@vercel/functions` in a generated project: `waitUntil`, which keeps a
3
+ * Vercel function running after its reply, and `attachDatabasePool`, which
4
+ * closes a pool's idle connections before the function is suspended.
5
+ *
6
+ * The generated code loads it inside a try, so a project that does not have
7
+ * it installed (or a bundler that cannot find it) keeps working without
8
+ * either. A project whose emitted files require it declares it (see
9
+ * `declareVercelFunctionsWhereRequired`).
10
+ */
11
+ export const VERCEL_FUNCTIONS_PACKAGE = '@vercel/functions'
12
+
13
+ // The 2.x line still supports Node 18, which the Next project template allows;
14
+ // 3.x requires Node 20. attachDatabasePool is stable from 2.2.8.
15
+ export const VERCEL_FUNCTIONS_VERSION = '^2.2.13'
16
+
17
+ const VERCEL_FUNCTIONS_REQUIRE = `require('${VERCEL_FUNCTIONS_PACKAGE}')`
18
+
19
+ /**
20
+ * `__vercelFunctions()` (the module, or null) and `__vercelWaitUntil()` (the
21
+ * module's waitUntil while this code runs inside a Vercel request, else null).
22
+ *
23
+ * waitUntil does nothing outside a Vercel request (a local `next dev`, another
24
+ * host): it reads the request context Vercel installs under
25
+ * `Symbol.for('@vercel/request-context')` and silently drops the promise when
26
+ * there is none. The same check here lets a caller await the work instead.
27
+ */
28
+ export const generateVercelFunctionsLoaderCode = (): string => `var __vercelFunctionsModule;
29
+ function __vercelFunctions() {
30
+ if (__vercelFunctionsModule === undefined) {
31
+ try { __vercelFunctionsModule = ${VERCEL_FUNCTIONS_REQUIRE}; } catch (_e) { __vercelFunctionsModule = null; }
32
+ }
33
+ return __vercelFunctionsModule;
34
+ }
35
+
36
+ function __vercelWaitUntil() {
37
+ var store = globalThis[Symbol.for('@vercel/request-context')];
38
+ var requestContext = store && typeof store.get === 'function' ? store.get() : null;
39
+ if (!requestContext || typeof requestContext.waitUntil !== 'function') return null;
40
+ var vercel = __vercelFunctions();
41
+ return vercel && typeof vercel.waitUntil === 'function' ? vercel.waitUntil : null;
42
+ }`
43
+
44
+ interface GeneratedFileRecord {
45
+ files?: Array<{ content?: unknown }>
46
+ }
47
+
48
+ /**
49
+ * Declares `@vercel/functions` in the project's dependencies when any emitted
50
+ * file requires it, keeping a version the project already declares.
51
+ */
52
+ export const declareVercelFunctionsWhereRequired = (
53
+ files: Map<string, GeneratedFileRecord>,
54
+ dependencies: Record<string, string>
55
+ ): void => {
56
+ if (dependencies[VERCEL_FUNCTIONS_PACKAGE]) {
57
+ return
58
+ }
59
+ for (const record of files.values()) {
60
+ for (const file of record.files || []) {
61
+ if (typeof file.content === 'string' && file.content.includes(VERCEL_FUNCTIONS_REQUIRE)) {
62
+ dependencies[VERCEL_FUNCTIONS_PACKAGE] = VERCEL_FUNCTIONS_VERSION
63
+ return
64
+ }
65
+ }
66
+ }
67
+ }
@@ -22,6 +22,12 @@ import {
22
22
  * released before the route replies; one left by a run that died expires
23
23
  * after a lease longer than any route may run.
24
24
  *
25
+ * The table is used when it exists (the editor creates it with the payment
26
+ * tables) and created only when it does not: a site's database role may read
27
+ * and write its tables without being allowed to create one, and
28
+ * CREATE TABLE IF NOT EXISTS asks for that permission even when the table is
29
+ * there.
30
+ *
25
31
  * Contract: `claimWebhookDelivery` never throws. Without Postgres, a driver,
26
32
  * the table or the privilege to create it, it claims nothing and lets the
27
33
  * delivery run — as every delivery ran before the lease existed.
@@ -48,6 +54,7 @@ ${generateLazyPgClientCode()}
48
54
  var TABLE_NAME = '${WEBHOOK_DELIVERY_LEASE_TABLE}';
49
55
  var LEASE_SECONDS = ${WEBHOOK_DELIVERY_LEASE_SECONDS};
50
56
  var QUERY_TIMEOUT_MS = 5000;
57
+ var TABLE_EXISTS_SQL = 'SELECT to_regclass($1) IS NOT NULL AS present';
51
58
  var CREATE_SQL = 'CREATE TABLE IF NOT EXISTS "' + TABLE_NAME + '" (' +
52
59
  'delivery_key TEXT PRIMARY KEY, claim_token TEXT NOT NULL, ' +
53
60
  'claimed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), attempts INTEGER NOT NULL DEFAULT 1)';
@@ -80,20 +87,28 @@ function __withTimeout(promise, ms) {
80
87
  return Promise.race([promise, timeout]).then(function (v) { clearTimeout(timer); return v; }, function (err) { clearTimeout(timer); throw err; });
81
88
  }
82
89
 
83
- async function __run(statements) {
90
+ // Runs \`work(query)\` on one connection, closed afterwards.
91
+ async function __run(work) {
84
92
  var client = getClient();
85
93
  await __withTimeout(client.connect(), QUERY_TIMEOUT_MS);
86
94
  try {
87
- var result = null;
88
- for (var i = 0; i < statements.length; i++) {
89
- result = await __withTimeout(client.query(statements[i].sql, statements[i].values), QUERY_TIMEOUT_MS);
90
- }
91
- return result;
95
+ return await work(function (sql, values) {
96
+ return __withTimeout(client.query(sql, values || []), QUERY_TIMEOUT_MS);
97
+ });
92
98
  } finally {
93
99
  try { await client.end(); } catch (_e) {}
94
100
  }
95
101
  }
96
102
 
103
+ async function __ensureTable(query) {
104
+ if (__tableReady) return;
105
+ var found = await query(TABLE_EXISTS_SQL, [TABLE_NAME]);
106
+ if (!found || !Array.isArray(found.rows) || !found.rows[0] || !found.rows[0].present) {
107
+ await query(CREATE_SQL);
108
+ }
109
+ __tableReady = true;
110
+ }
111
+
97
112
  function __stableJson(value) {
98
113
  try { return JSON.stringify(value === undefined ? null : value) || ''; } catch (_e) { return ''; }
99
114
  }
@@ -120,12 +135,12 @@ async function claimWebhookDelivery(scope, rawBody, verifiedBody) {
120
135
  .update(__stableJson(verifiedBody))
121
136
  .digest('hex');
122
137
  var token = crypto.randomBytes(16).toString('hex');
123
- var statements = __tableReady ? [] : [{ sql: CREATE_SQL, values: [] }];
124
- statements.push({ sql: CLAIM_SQL, values: [key, token, LEASE_SECONDS] });
125
138
  var result;
126
139
  try {
127
- result = await __run(statements);
128
- __tableReady = true;
140
+ result = await __run(async function (query) {
141
+ await __ensureTable(query);
142
+ return query(CLAIM_SQL, [key, token, LEASE_SECONDS]);
143
+ });
129
144
  } catch (err) {
130
145
  var code = err && err.code;
131
146
  if (code === '42501') { __disable('no privilege to create or write table ' + TABLE_NAME); }
@@ -138,7 +153,7 @@ async function claimWebhookDelivery(scope, rawBody, verifiedBody) {
138
153
  return {
139
154
  claimed: true,
140
155
  release: function () {
141
- return __run([{ sql: RELEASE_SQL, values: [key, token] }]).then(
156
+ return __run(function (query) { return query(RELEASE_SQL, [key, token]); }).then(
142
157
  function () {},
143
158
  function (err) { console.error('[webhook-delivery-lease] release failed: ' + (err && err.message ? err.message : String(err))); }
144
159
  );
@@ -72,6 +72,7 @@ import {
72
72
  import { generateInvoiceFiles, resolveInvoiceDataSource } from './invoice'
73
73
  import { ensureSentEmailLogModule, hasEmailSendingNodeType } from './sent-email-log'
74
74
  import { ensureWebhookDeliveryLeaseModule } from './webhook-delivery-lease'
75
+ import { declareVercelFunctionsWhereRequired } from './vercel-functions'
75
76
  import { ensureEmailLocaleModule, resolveEmailLocaleConfig } from './email-locale'
76
77
  import { workflowUtilsImportLine, workflowUtilsRequireLine } from './workflow-utils-alias'
77
78
  import { generateWebhookFiles } from './webhook-generator'
@@ -385,6 +386,7 @@ export class NextWorkflowProjectPlugin implements ProjectPlugin {
385
386
  if (hasAuthentication) {
386
387
  this.injectSessionProviderIntoApp(structure)
387
388
  }
389
+ declareVercelFunctionsWhereRequired(files, dependencies)
388
390
  return structure
389
391
  }
390
392
 
@@ -974,6 +976,7 @@ export class NextWorkflowProjectPlugin implements ProjectPlugin {
974
976
  this.injectSessionProviderIntoApp(structure)
975
977
  }
976
978
 
979
+ declareVercelFunctionsWhereRequired(files, dependencies)
977
980
  return structure
978
981
  }
979
982