@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
@@ -0,0 +1,306 @@
1
+ import type { UIDLWorkflow, UIDLWorkflowEdge, UIDLWorkflowNode } from '@teleporthq/teleport-types'
2
+ import { splitIntoSegments } from '../src/segment-splitter'
3
+ import type { WorkflowSegment } from '../src/types'
4
+
5
+ // Every server segment the browser reaches is one awaited round trip. A
6
+ // checkout is one long chain of server steps with a refusal branch (a toast, a
7
+ // reset) at almost every gate, all at the same depth as the chain's next step.
8
+ // Ordered by depth alone, each refusal step sat between two server steps of the
9
+ // chain and cut it into one more round trip; the loop over the cart lines held
10
+ // everything after it in a depth-only order. Built here the way the editor's
11
+ // checkout builds Place Order, with synthetic ids and labels.
12
+
13
+ type Kind = 'client' | 'server' | 'gate' | 'loop' | 'custom-node' | 'navigate'
14
+
15
+ const TYPE: Record<Kind, string> = {
16
+ client: 'state-update-local-state',
17
+ server: 'data-raw-query',
18
+ gate: 'general-if-statement',
19
+ loop: 'general-loop',
20
+ 'custom-node': 'general-custom-node',
21
+ navigate: 'navigation-go-to-page',
22
+ }
23
+
24
+ const ENV: Record<Kind, UIDLWorkflowNode['executionEnv']> = {
25
+ client: 'client',
26
+ server: 'server',
27
+ gate: 'universal',
28
+ loop: 'universal',
29
+ 'custom-node': 'universal',
30
+ navigate: 'client',
31
+ }
32
+
33
+ const buildWorkflow = (
34
+ steps: Array<[string, Kind]>,
35
+ links: Array<[string, string, string?, string?]>
36
+ ): UIDLWorkflow => {
37
+ const edges: UIDLWorkflowEdge[] = links.map(([source, target, sourceHandle, targetHandle]) => ({
38
+ id: `${source}->${target}`,
39
+ source,
40
+ target,
41
+ ...(sourceHandle ? { sourceHandle } : {}),
42
+ ...(targetHandle ? { targetHandle } : {}),
43
+ }))
44
+ // The editor's step numbers: breadth-first depth from the trigger.
45
+ const depth = new Map<string, number>()
46
+ const queue: Array<[string, number]> = [['trigger', 0]]
47
+ while (queue.length > 0) {
48
+ const [id, d] = queue.shift() as [string, number]
49
+ if (depth.has(id)) {
50
+ continue
51
+ }
52
+ depth.set(id, d)
53
+ edges.filter((e) => e.source === id).forEach((e) => queue.push([e.target, d + 1]))
54
+ }
55
+ return {
56
+ id: 'place-order',
57
+ name: 'Place Order',
58
+ trigger: { type: 'event-form-submitted', nodeId: 'trigger' },
59
+ nodes: steps.map(([id, kind]) => ({
60
+ id,
61
+ type: TYPE[kind],
62
+ label: id,
63
+ config: {},
64
+ executionEnv: ENV[kind],
65
+ stepNumber: depth.get(id) || 0,
66
+ })),
67
+ edges,
68
+ } as unknown as UIDLWorkflow
69
+ }
70
+
71
+ // A gate whose condition reads a server step, its refusal branch three or four
72
+ // browser steps long, its accepted branch the rest of the chain.
73
+ const placeOrder = buildWorkflow(
74
+ [
75
+ ['setBusy', 'client'],
76
+ ['formValid', 'gate'],
77
+ ['formToast', 'client'],
78
+ ['fetchStock', 'server'],
79
+ ['checkStock', 'server'],
80
+ ['inStock', 'gate'],
81
+ ['stockToast', 'client'],
82
+ ['stockReset', 'client'],
83
+ ['resolveUser', 'custom-node'],
84
+ ['loadDiscounts', 'server'],
85
+ ['countOrders', 'server'],
86
+ ['recheckVoucher', 'server'],
87
+ ['voucherValid', 'gate'],
88
+ ['forgetVoucher', 'client'],
89
+ ['voucherToast', 'client'],
90
+ ['voucherReset', 'client'],
91
+ ['voucherClear', 'client'],
92
+ ['priceOrder', 'server'],
93
+ ['assemble', 'server'],
94
+ ['checkAddress', 'server'],
95
+ ['canBill', 'gate'],
96
+ ['billToast', 'client'],
97
+ ['billReset', 'client'],
98
+ ['createOrder', 'server'],
99
+ ['redeem', 'server'],
100
+ ['redeemed', 'gate'],
101
+ ['cancelOrder', 'server'],
102
+ ['redeemToast', 'client'],
103
+ ['orderNumber', 'server'],
104
+ ['eachLine', 'loop'],
105
+ ['buildLine', 'server'],
106
+ ['saveLine', 'server'],
107
+ ['decrementStock', 'server'],
108
+ ['recordPlaced', 'custom-node'],
109
+ ['saveDetails', 'server'],
110
+ ['settled', 'gate'],
111
+ ['markSettled', 'server'],
112
+ ['clearCart', 'client'],
113
+ ['emailPayload', 'server'],
114
+ ['sendEmail', 'server'],
115
+ ['goToOrder', 'navigate'],
116
+ ['redirectUrls', 'server'],
117
+ ['charge', 'server'],
118
+ ['opened', 'gate'],
119
+ ['cancelRefused', 'server'],
120
+ ['refusedToast', 'client'],
121
+ ['recordCheckout', 'server'],
122
+ ['sendToCheckout', 'navigate'],
123
+ ],
124
+ [
125
+ ['trigger', 'setBusy'],
126
+ ['setBusy', 'formValid'],
127
+ ['formValid', 'formToast', 'false'],
128
+ ['formValid', 'fetchStock', 'true'],
129
+ ['fetchStock', 'checkStock'],
130
+ ['checkStock', 'inStock'],
131
+ ['inStock', 'stockToast', 'false'],
132
+ ['stockToast', 'stockReset'],
133
+ ['inStock', 'resolveUser', 'true'],
134
+ ['resolveUser', 'loadDiscounts'],
135
+ ['loadDiscounts', 'countOrders'],
136
+ ['countOrders', 'recheckVoucher'],
137
+ ['recheckVoucher', 'voucherValid'],
138
+ ['voucherValid', 'forgetVoucher', 'false'],
139
+ ['forgetVoucher', 'voucherToast'],
140
+ ['voucherToast', 'voucherReset'],
141
+ ['voucherReset', 'voucherClear'],
142
+ ['voucherValid', 'priceOrder', 'true'],
143
+ ['priceOrder', 'assemble'],
144
+ ['assemble', 'checkAddress'],
145
+ ['checkAddress', 'canBill'],
146
+ ['canBill', 'billToast', 'false'],
147
+ ['billToast', 'billReset'],
148
+ ['canBill', 'createOrder', 'true'],
149
+ ['createOrder', 'redeem'],
150
+ ['redeem', 'redeemed'],
151
+ ['redeemed', 'cancelOrder', 'false'],
152
+ ['cancelOrder', 'redeemToast'],
153
+ ['redeemed', 'orderNumber', 'true'],
154
+ ['orderNumber', 'eachLine'],
155
+ ['eachLine', 'buildLine', 'loop', 'loop-body-in'],
156
+ ['buildLine', 'saveLine'],
157
+ ['saveLine', 'eachLine', 'loop-body-out', 'loop-back'],
158
+ ['eachLine', 'decrementStock', 'exit'],
159
+ ['decrementStock', 'recordPlaced'],
160
+ ['recordPlaced', 'saveDetails'],
161
+ ['saveDetails', 'settled'],
162
+ ['settled', 'markSettled', 'true'],
163
+ ['markSettled', 'clearCart'],
164
+ ['clearCart', 'emailPayload'],
165
+ ['emailPayload', 'sendEmail'],
166
+ ['sendEmail', 'goToOrder'],
167
+ ['settled', 'redirectUrls', 'false'],
168
+ ['redirectUrls', 'charge'],
169
+ ['charge', 'opened'],
170
+ ['opened', 'cancelRefused', 'false'],
171
+ ['cancelRefused', 'refusedToast'],
172
+ ['opened', 'recordCheckout', 'true'],
173
+ ['recordCheckout', 'sendToCheckout'],
174
+ ]
175
+ )
176
+
177
+ const REFUSALS = [
178
+ 'formToast',
179
+ 'stockToast',
180
+ 'stockReset',
181
+ 'forgetVoucher',
182
+ 'voucherToast',
183
+ 'voucherReset',
184
+ 'voucherClear',
185
+ 'billToast',
186
+ 'billReset',
187
+ 'cancelOrder',
188
+ 'redeemToast',
189
+ 'cancelRefused',
190
+ 'refusedToast',
191
+ ]
192
+ const CASH_ONLY = ['markSettled', 'clearCart', 'emailPayload', 'sendEmail', 'goToOrder']
193
+ const ONLINE_ONLY = ['redirectUrls', 'charge', 'opened', 'recordCheckout', 'sendToCheckout']
194
+
195
+ // The server segments an order that every gate accepts calls, in order.
196
+ const roundTrips = (segments: WorkflowSegment[], skipped: string[]): string[][] =>
197
+ segments
198
+ .filter((s) => s.env === 'server')
199
+ .map((s) => s.nodes.map((n) => n.id).filter((id) => !skipped.includes(id)))
200
+ .filter((ids) => ids.length > 0)
201
+
202
+ const segmentOf = (segments: WorkflowSegment[], id: string): WorkflowSegment =>
203
+ segments.find((s) => s.nodes.some((n) => n.id === id)) as WorkflowSegment
204
+
205
+ describe('splitIntoSegments — a checkout waits on as few server round trips as it can', () => {
206
+ const segments = splitIntoSegments(placeOrder)
207
+
208
+ it('calls the server once between each pair of steps that need the browser', () => {
209
+ // Browser steps on the accepted path: resolveUser (custom node) and
210
+ // recordPlaced (custom node) — plus clearCart for a cash order.
211
+ expect(roundTrips(segments, [...REFUSALS, ...ONLINE_ONLY])).toEqual([
212
+ ['fetchStock', 'checkStock', 'inStock'],
213
+ [
214
+ 'loadDiscounts',
215
+ 'countOrders',
216
+ 'recheckVoucher',
217
+ 'voucherValid',
218
+ 'priceOrder',
219
+ 'assemble',
220
+ 'checkAddress',
221
+ 'canBill',
222
+ 'createOrder',
223
+ 'redeem',
224
+ 'redeemed',
225
+ 'orderNumber',
226
+ 'eachLine',
227
+ 'buildLine',
228
+ 'saveLine',
229
+ 'decrementStock',
230
+ ],
231
+ ['saveDetails', 'settled', 'markSettled'],
232
+ ['emailPayload', 'sendEmail'],
233
+ ])
234
+ expect(roundTrips(segments, [...REFUSALS, ...CASH_ONLY])).toHaveLength(3)
235
+ })
236
+
237
+ it('decides every gate that reads a server step inside the server route', () => {
238
+ // A gate the browser decides is one a crafted request can answer: the
239
+ // route then takes the browser's word for which branch was skipped.
240
+ for (const gate of ['inStock', 'voucherValid', 'canBill', 'redeemed', 'settled', 'opened']) {
241
+ expect(segmentOf(segments, gate).env).toBe('server')
242
+ }
243
+ // A refusal step on the server shares its gate's route, which skips it itself.
244
+ expect(segmentOf(segments, 'cancelOrder')).toBe(segmentOf(segments, 'redeemed'))
245
+ expect(segmentOf(segments, 'cancelRefused')).toBe(segmentOf(segments, 'opened'))
246
+ })
247
+
248
+ it('keeps the loop and its body in one segment, in order', () => {
249
+ const loopSegment = segmentOf(segments, 'eachLine')
250
+ const ids = loopSegment.nodes.map((n) => n.id)
251
+ expect(ids.slice(ids.indexOf('eachLine'), ids.indexOf('eachLine') + 3)).toEqual([
252
+ 'eachLine',
253
+ 'buildLine',
254
+ 'saveLine',
255
+ ])
256
+ expect(ids.indexOf('decrementStock')).toBeGreaterThan(ids.indexOf('saveLine'))
257
+ })
258
+
259
+ it('runs every step after everything it depends on', () => {
260
+ const order = segments.flatMap((s) => s.nodes.map((n) => n.id))
261
+ for (const e of placeOrder.edges) {
262
+ if (e.targetHandle === 'loop-back' || e.source === 'trigger') {
263
+ continue
264
+ }
265
+ expect([e.source, order.indexOf(e.source) < order.indexOf(e.target)]).toEqual([
266
+ e.source,
267
+ true,
268
+ ])
269
+ }
270
+ })
271
+ })
272
+
273
+ describe('splitIntoSegments — a server route runs its steps by step number', () => {
274
+ it('never puts a step in the segment of an input with a later step number', () => {
275
+ // `total` hangs off the trigger (step 1) but reads `line3` (step 3): the
276
+ // route, which sorts its segment by step, would run it before that value.
277
+ const workflow = {
278
+ id: 'w',
279
+ name: 'w',
280
+ trigger: { type: 'event-element-clicked', nodeId: 'trigger' },
281
+ nodes: [
282
+ { id: 'line1', type: 'data-select', stepNumber: 1, executionEnv: 'server', config: {} },
283
+ { id: 'line2', type: 'data-select', stepNumber: 2, executionEnv: 'server', config: {} },
284
+ { id: 'line3', type: 'data-select', stepNumber: 3, executionEnv: 'server', config: {} },
285
+ {
286
+ id: 'total',
287
+ type: 'data-create-item',
288
+ stepNumber: 1,
289
+ executionEnv: 'server',
290
+ config: { columnMappings: [{ value: { type: 'workflowContext', nodeId: 'line3' } }] },
291
+ },
292
+ ],
293
+ edges: [
294
+ { id: 'e0', source: 'trigger', target: 'line1' },
295
+ { id: 'e1', source: 'line1', target: 'line2' },
296
+ { id: 'e2', source: 'line2', target: 'line3' },
297
+ { id: 'e3', source: 'trigger', target: 'total' },
298
+ ],
299
+ } as unknown as UIDLWorkflow
300
+ const segments = splitIntoSegments(workflow)
301
+ expect(segments.map((s) => [s.env, s.nodes.map((n) => n.id)])).toEqual([
302
+ ['server', ['line1', 'line2', 'line3']],
303
+ ['server', ['total']],
304
+ ])
305
+ })
306
+ })
@@ -15,8 +15,10 @@
15
15
  // reproduced live against a published chat bundle.
16
16
  //
17
17
  // The fix: emitted nodes carry `executionEnv`, and the client runtime filters
18
- // on-stream / on-end branch execution down to client-executable nodes. Server
19
- // branch nodes stay server-side where they already run with full configs.
18
+ // on-stream branch execution down to client-executable nodes. Server branch
19
+ // nodes stay server-side where they already run with full configs. The on-end
20
+ // branch is no longer run by the 'done' handler at all: its steps run in their
21
+ // own segments, in edge order (see streaming-on-end-order.test.ts).
20
22
 
21
23
  import {
22
24
  generateSharedRuntimeUtilsCode,
@@ -167,7 +169,7 @@ describe('callStreamingServerSegment — on-stream/on-end env filter', () => {
167
169
  global.fetch = originalFetch
168
170
  })
169
171
 
170
- it('executes ONLY client-executable branch nodes client-side; server branch nodes are skipped but still marked handled', async () => {
172
+ it('runs the on-stream client nodes per chunk and leaves the whole on-end branch to its segments', async () => {
171
173
  const { runtime } = loadRuntimeModules()
172
174
  const nodes = clientBundleNodes()
173
175
  const edges = workflowEdges()
@@ -211,20 +213,15 @@ describe('callStreamingServerSegment — on-stream/on-end env filter', () => {
211
213
  'exec-1'
212
214
  )
213
215
 
214
- // Client branch nodes ran (on-stream state update + on-end state update).
215
- expect(calls).toContain('state:answer')
216
- expect(calls).toContain('state:done')
217
- // Server branch nodes were NEVER executed with client handlers.
218
- expect(calls).not.toContain('general-custom-js')
219
- expect(calls).not.toContain('data-raw-query')
220
- // All on-end branch nodes stay marked handled so later client segments
221
- // do not re-run them (unchanged bookkeeping semantics).
222
- expect(handled[ON_END_SERVER_JS_ID]).toBe(true)
223
- expect(handled[ON_END_SERVER_SQL_ID]).toBe(true)
224
- expect(handled[ON_END_CLIENT_ID]).toBe(true)
216
+ // Only the on-stream state update ran; server nodes never ran with client
217
+ // handlers, and the on-end client node waits for the server steps before it.
218
+ expect(calls).toEqual(['state:answer'])
219
+ expect(handled[ON_STREAM_CLIENT_ID]).toBe(true)
220
+ // Not marked handled, so its own client segment still runs it.
221
+ expect(handled[ON_END_CLIENT_ID]).toBeUndefined()
225
222
  })
226
223
 
227
- it('keeps executing branch nodes WITHOUT an executionEnv marker (older bundles)', async () => {
224
+ it('keeps executing on-stream nodes WITHOUT an executionEnv marker (older bundles)', async () => {
228
225
  const { runtime } = loadRuntimeModules()
229
226
  const nodes = clientBundleNodes().map((n) => {
230
227
  const { executionEnv, ...rest } = n
@@ -268,9 +265,12 @@ describe('callStreamingServerSegment — on-stream/on-end env filter', () => {
268
265
  'exec-2'
269
266
  )
270
267
 
271
- // Fail-open: without the marker, behavior is unchanged from older bundles.
272
- expect(calls).toContain('general-custom-js')
273
- expect(calls).toContain('data-raw-query')
268
+ // Fail-open: an unmarked on-stream node still runs per chunk.
269
+ expect(calls).toContain('state:answer')
270
+ // The on-end branch runs in its own segments, marker or not.
271
+ expect(calls).not.toContain('general-custom-js')
272
+ expect(calls).not.toContain('data-raw-query')
273
+ expect(calls).not.toContain('state:done')
274
274
  })
275
275
  })
276
276