@teleporthq/teleport-plugin-next-workflows 0.43.53 → 0.43.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/__tests__/ai-chat-rag-sql-parameterization.test.ts +237 -0
- package/__tests__/ai-embedding-optional.test.ts +77 -0
- package/__tests__/ai-model-capabilities.test.ts +234 -0
- package/__tests__/ai-provider-coverage.test.ts +183 -0
- package/__tests__/cart-clear-db-mirror.test.ts +45 -0
- package/__tests__/cart-get-total-storefront-tax.test.ts +165 -0
- package/__tests__/cron-route-loop-templateparams.test.ts +15 -6
- package/__tests__/data-create-item-order-notification.test.ts +33 -0
- package/__tests__/ecommerce-customhandler-rewriter.test.ts +1 -1
- package/__tests__/internal-request-headers.test.ts +159 -0
- package/__tests__/order-details-route.test.ts +238 -0
- package/__tests__/resolve-config-template-params.test.ts +174 -0
- package/__tests__/utility-nodes.test.ts +27 -11
- package/dist/cjs/api-route-generator.d.ts.map +1 -1
- package/dist/cjs/api-route-generator.js +27 -15
- package/dist/cjs/api-route-generator.js.map +1 -1
- package/dist/cjs/ecommerce/order-details-route.d.ts +18 -0
- package/dist/cjs/ecommerce/order-details-route.d.ts.map +1 -0
- package/dist/cjs/ecommerce/order-details-route.js +172 -0
- package/dist/cjs/ecommerce/order-details-route.js.map +1 -0
- package/dist/cjs/ecommerce-customhandler-rewriter.d.ts +1 -1
- package/dist/cjs/ecommerce-customhandler-rewriter.d.ts.map +1 -1
- package/dist/cjs/ecommerce-customhandler-rewriter.js +20 -4
- package/dist/cjs/ecommerce-customhandler-rewriter.js.map +1 -1
- package/dist/cjs/executor-generator.d.ts.map +1 -1
- package/dist/cjs/executor-generator.js +69 -11
- package/dist/cjs/executor-generator.js.map +1 -1
- package/dist/cjs/nodes/ai/ai-custom-prompt.js +1 -1
- package/dist/cjs/nodes/ai/ai-custom-prompt.js.map +1 -1
- package/dist/cjs/nodes/ai/ai-detect-language.js +1 -1
- package/dist/cjs/nodes/ai/ai-detect-language.js.map +1 -1
- package/dist/cjs/nodes/ai/ai-generate-text-embedding.d.ts.map +1 -1
- package/dist/cjs/nodes/ai/ai-generate-text-embedding.js +47 -4
- package/dist/cjs/nodes/ai/ai-generate-text-embedding.js.map +1 -1
- package/dist/cjs/nodes/ai/ai-provider-utils.d.ts +2 -0
- package/dist/cjs/nodes/ai/ai-provider-utils.d.ts.map +1 -1
- package/dist/cjs/nodes/ai/ai-provider-utils.js +195 -106
- package/dist/cjs/nodes/ai/ai-provider-utils.js.map +1 -1
- package/dist/cjs/nodes/ai/ai-sentiment-analysis.js +1 -1
- package/dist/cjs/nodes/ai/ai-sentiment-analysis.js.map +1 -1
- package/dist/cjs/nodes/ai/ai-summarization.js +1 -1
- package/dist/cjs/nodes/ai/ai-summarization.js.map +1 -1
- package/dist/cjs/nodes/ai/ai-text-classifier.js +1 -1
- package/dist/cjs/nodes/ai/ai-text-classifier.js.map +1 -1
- package/dist/cjs/nodes/ai/ai-text-transform.js +1 -1
- package/dist/cjs/nodes/ai/ai-text-transform.js.map +1 -1
- package/dist/cjs/nodes/cart/cart-clear.d.ts.map +1 -1
- package/dist/cjs/nodes/cart/cart-clear.js +40 -0
- package/dist/cjs/nodes/cart/cart-clear.js.map +1 -1
- package/dist/cjs/nodes/cart/cart-get-total.d.ts.map +1 -1
- package/dist/cjs/nodes/cart/cart-get-total.js +56 -15
- package/dist/cjs/nodes/cart/cart-get-total.js.map +1 -1
- package/dist/cjs/nodes/data/data-count.d.ts.map +1 -1
- package/dist/cjs/nodes/data/data-count.js +6 -1
- package/dist/cjs/nodes/data/data-count.js.map +1 -1
- package/dist/cjs/nodes/data/data-create-item.d.ts.map +1 -1
- package/dist/cjs/nodes/data/data-create-item.js +45 -9
- package/dist/cjs/nodes/data/data-create-item.js.map +1 -1
- package/dist/cjs/nodes/data/data-delete-item.d.ts.map +1 -1
- package/dist/cjs/nodes/data/data-delete-item.js +7 -4
- package/dist/cjs/nodes/data/data-delete-item.js.map +1 -1
- package/dist/cjs/nodes/data/data-raw-query.d.ts.map +1 -1
- package/dist/cjs/nodes/data/data-raw-query.js +7 -1
- package/dist/cjs/nodes/data/data-raw-query.js.map +1 -1
- package/dist/cjs/nodes/data/data-select.d.ts.map +1 -1
- package/dist/cjs/nodes/data/data-select.js +6 -1
- package/dist/cjs/nodes/data/data-select.js.map +1 -1
- package/dist/cjs/nodes/data/data-update-item.d.ts.map +1 -1
- package/dist/cjs/nodes/data/data-update-item.js +7 -4
- package/dist/cjs/nodes/data/data-update-item.js.map +1 -1
- package/dist/cjs/nodes/utility/utility-hybrid-search.js +2 -2
- package/dist/cjs/nodes/utility/utility-hybrid-search.js.map +1 -1
- package/dist/cjs/nodes/utility/utility-semantic-search.js +1 -1
- package/dist/cjs/nodes/utility/utility-semantic-search.js.map +1 -1
- package/dist/cjs/nodes/utility/utility-similarity-scoring.d.ts.map +1 -1
- package/dist/cjs/nodes/utility/utility-similarity-scoring.js +49 -3
- package/dist/cjs/nodes/utility/utility-similarity-scoring.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/cjs/workflow-project-plugin.d.ts.map +1 -1
- package/dist/cjs/workflow-project-plugin.js +10 -4
- package/dist/cjs/workflow-project-plugin.js.map +1 -1
- package/dist/esm/api-route-generator.d.ts.map +1 -1
- package/dist/esm/api-route-generator.js +27 -15
- package/dist/esm/api-route-generator.js.map +1 -1
- package/dist/esm/ecommerce/order-details-route.d.ts +18 -0
- package/dist/esm/ecommerce/order-details-route.d.ts.map +1 -0
- package/dist/esm/ecommerce/order-details-route.js +167 -0
- package/dist/esm/ecommerce/order-details-route.js.map +1 -0
- package/dist/esm/ecommerce-customhandler-rewriter.d.ts +1 -1
- package/dist/esm/ecommerce-customhandler-rewriter.d.ts.map +1 -1
- package/dist/esm/ecommerce-customhandler-rewriter.js +20 -4
- package/dist/esm/ecommerce-customhandler-rewriter.js.map +1 -1
- package/dist/esm/executor-generator.d.ts.map +1 -1
- package/dist/esm/executor-generator.js +69 -11
- package/dist/esm/executor-generator.js.map +1 -1
- package/dist/esm/nodes/ai/ai-custom-prompt.js +1 -1
- package/dist/esm/nodes/ai/ai-custom-prompt.js.map +1 -1
- package/dist/esm/nodes/ai/ai-detect-language.js +1 -1
- package/dist/esm/nodes/ai/ai-detect-language.js.map +1 -1
- package/dist/esm/nodes/ai/ai-generate-text-embedding.d.ts.map +1 -1
- package/dist/esm/nodes/ai/ai-generate-text-embedding.js +47 -4
- package/dist/esm/nodes/ai/ai-generate-text-embedding.js.map +1 -1
- package/dist/esm/nodes/ai/ai-provider-utils.d.ts +2 -0
- package/dist/esm/nodes/ai/ai-provider-utils.d.ts.map +1 -1
- package/dist/esm/nodes/ai/ai-provider-utils.js +194 -105
- package/dist/esm/nodes/ai/ai-provider-utils.js.map +1 -1
- package/dist/esm/nodes/ai/ai-sentiment-analysis.js +1 -1
- package/dist/esm/nodes/ai/ai-sentiment-analysis.js.map +1 -1
- package/dist/esm/nodes/ai/ai-summarization.js +1 -1
- package/dist/esm/nodes/ai/ai-summarization.js.map +1 -1
- package/dist/esm/nodes/ai/ai-text-classifier.js +1 -1
- package/dist/esm/nodes/ai/ai-text-classifier.js.map +1 -1
- package/dist/esm/nodes/ai/ai-text-transform.js +1 -1
- package/dist/esm/nodes/ai/ai-text-transform.js.map +1 -1
- package/dist/esm/nodes/cart/cart-clear.d.ts.map +1 -1
- package/dist/esm/nodes/cart/cart-clear.js +40 -0
- package/dist/esm/nodes/cart/cart-clear.js.map +1 -1
- package/dist/esm/nodes/cart/cart-get-total.d.ts.map +1 -1
- package/dist/esm/nodes/cart/cart-get-total.js +56 -15
- package/dist/esm/nodes/cart/cart-get-total.js.map +1 -1
- package/dist/esm/nodes/data/data-count.d.ts.map +1 -1
- package/dist/esm/nodes/data/data-count.js +6 -1
- package/dist/esm/nodes/data/data-count.js.map +1 -1
- package/dist/esm/nodes/data/data-create-item.d.ts.map +1 -1
- package/dist/esm/nodes/data/data-create-item.js +45 -9
- package/dist/esm/nodes/data/data-create-item.js.map +1 -1
- package/dist/esm/nodes/data/data-delete-item.d.ts.map +1 -1
- package/dist/esm/nodes/data/data-delete-item.js +7 -4
- package/dist/esm/nodes/data/data-delete-item.js.map +1 -1
- package/dist/esm/nodes/data/data-raw-query.d.ts.map +1 -1
- package/dist/esm/nodes/data/data-raw-query.js +7 -1
- package/dist/esm/nodes/data/data-raw-query.js.map +1 -1
- package/dist/esm/nodes/data/data-select.d.ts.map +1 -1
- package/dist/esm/nodes/data/data-select.js +6 -1
- package/dist/esm/nodes/data/data-select.js.map +1 -1
- package/dist/esm/nodes/data/data-update-item.d.ts.map +1 -1
- package/dist/esm/nodes/data/data-update-item.js +7 -4
- package/dist/esm/nodes/data/data-update-item.js.map +1 -1
- package/dist/esm/nodes/utility/utility-hybrid-search.js +2 -2
- package/dist/esm/nodes/utility/utility-hybrid-search.js.map +1 -1
- package/dist/esm/nodes/utility/utility-semantic-search.js +1 -1
- package/dist/esm/nodes/utility/utility-semantic-search.js.map +1 -1
- package/dist/esm/nodes/utility/utility-similarity-scoring.d.ts.map +1 -1
- package/dist/esm/nodes/utility/utility-similarity-scoring.js +49 -3
- package/dist/esm/nodes/utility/utility-similarity-scoring.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/workflow-project-plugin.d.ts.map +1 -1
- package/dist/esm/workflow-project-plugin.js +10 -4
- package/dist/esm/workflow-project-plugin.js.map +1 -1
- package/package.json +4 -4
- package/src/api-route-generator.ts +27 -15
- package/src/ecommerce/order-details-route.ts +195 -0
- package/src/ecommerce-customhandler-rewriter.ts +24 -4
- package/src/executor-generator.ts +69 -11
- package/src/nodes/ai/ai-custom-prompt.ts +2 -2
- package/src/nodes/ai/ai-detect-language.ts +2 -2
- package/src/nodes/ai/ai-generate-text-embedding.ts +49 -4
- package/src/nodes/ai/ai-provider-utils.ts +216 -106
- package/src/nodes/ai/ai-sentiment-analysis.ts +2 -2
- package/src/nodes/ai/ai-summarization.ts +2 -2
- package/src/nodes/ai/ai-text-classifier.ts +2 -2
- package/src/nodes/ai/ai-text-transform.ts +2 -2
- package/src/nodes/cart/cart-clear.ts +40 -0
- package/src/nodes/cart/cart-get-total.ts +61 -17
- package/src/nodes/data/data-count.ts +6 -1
- package/src/nodes/data/data-create-item.ts +45 -9
- package/src/nodes/data/data-delete-item.ts +6 -0
- package/src/nodes/data/data-raw-query.ts +8 -1
- package/src/nodes/data/data-select.ts +6 -1
- package/src/nodes/data/data-update-item.ts +6 -0
- package/src/nodes/utility/utility-hybrid-search.ts +2 -2
- package/src/nodes/utility/utility-semantic-search.ts +1 -1
- package/src/nodes/utility/utility-similarity-scoring.ts +56 -3
- package/src/workflow-project-plugin.ts +10 -4
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { ProjectUIDL } from '@teleporthq/teleport-types'
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_ORDER_DETAILS_PREFIX,
|
|
4
|
+
readSuccessUrlPrefix,
|
|
5
|
+
resolveOrderDetailsRoutePrefix,
|
|
6
|
+
} from '../src/ecommerce/order-details-route'
|
|
7
|
+
import { __testables, rewriteLowStockCustomHandlers } from '../src/ecommerce-customhandler-rewriter'
|
|
8
|
+
|
|
9
|
+
const { buildPaymentMetadataBuilder } = __testables
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* ⛔ THE REPORTED DEFECT (run c133d485): a completed Stripe checkout redirected to
|
|
13
|
+
* `/order-details/ORD-1?payment=success` — a 404 — because the payment-metadata
|
|
14
|
+
* rewrite hard-coded that prefix while the site map routed the order-details page
|
|
15
|
+
* at `/orders/[order_number]`. The COD path was fine: it navigates through
|
|
16
|
+
* `navigation-go-to-page`, which reads the page's real route. Both paths must now
|
|
17
|
+
* read the SAME field.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const goToOrderDetails = (overrides: Record<string, unknown> = {}) => ({
|
|
21
|
+
id: 'nav-1',
|
|
22
|
+
type: 'navigation-go-to-page',
|
|
23
|
+
config: {
|
|
24
|
+
pageId: '/orders/[order_number]',
|
|
25
|
+
targetPage: {
|
|
26
|
+
pageId: 'TQ_Spm9dxZZx6',
|
|
27
|
+
pageName: 'order-details',
|
|
28
|
+
staticUrl: '/orders',
|
|
29
|
+
isDetailsPage: true,
|
|
30
|
+
differentiatorColumn: 'order_number',
|
|
31
|
+
rowOwnerTable: 'teleport_orders',
|
|
32
|
+
...overrides,
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
const uidlWithNodes = (nodes: unknown[]): ProjectUIDL =>
|
|
38
|
+
({
|
|
39
|
+
workflows: { workflows: { placeOrder: { id: 'placeOrder', nodes } } },
|
|
40
|
+
} as unknown as ProjectUIDL)
|
|
41
|
+
|
|
42
|
+
describe('resolveOrderDetailsRoutePrefix', () => {
|
|
43
|
+
it('reads the prefix from the order-details navigation node', () => {
|
|
44
|
+
expect(resolveOrderDetailsRoutePrefix(uidlWithNodes([goToOrderDetails()]))).toBe('/orders')
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('recognises the page by its orders row-owner table even when the name differs', () => {
|
|
48
|
+
const uidl = uidlWithNodes([
|
|
49
|
+
goToOrderDetails({ pageName: 'purchase-receipt', staticUrl: '/account/receipts' }),
|
|
50
|
+
])
|
|
51
|
+
expect(resolveOrderDetailsRoutePrefix(uidl)).toBe('/account/receipts')
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('recognises the page by name when no row-owner table is stamped', () => {
|
|
55
|
+
const uidl = uidlWithNodes([
|
|
56
|
+
goToOrderDetails({ rowOwnerTable: undefined, staticUrl: '/shop/orders' }),
|
|
57
|
+
])
|
|
58
|
+
expect(resolveOrderDetailsRoutePrefix(uidl)).toBe('/shop/orders')
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('falls back to the literal route, minus its dynamic segment, when staticUrl is unusable', () => {
|
|
62
|
+
const uidl = uidlWithNodes([goToOrderDetails({ staticUrl: '' })])
|
|
63
|
+
expect(resolveOrderDetailsRoutePrefix(uidl)).toBe('/orders')
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('never returns a staticUrl that still carries a dynamic segment', () => {
|
|
67
|
+
const uidl = uidlWithNodes([goToOrderDetails({ staticUrl: '/orders/[order_number]' })])
|
|
68
|
+
// Unusable as a prefix, so the pageId fallback answers instead.
|
|
69
|
+
expect(resolveOrderDetailsRoutePrefix(uidl)).toBe('/orders')
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('strips a trailing slash', () => {
|
|
73
|
+
const uidl = uidlWithNodes([goToOrderDetails({ staticUrl: '/orders/' })])
|
|
74
|
+
expect(resolveOrderDetailsRoutePrefix(uidl)).toBe('/orders')
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('ignores navigation to OTHER details pages', () => {
|
|
78
|
+
const uidl = uidlWithNodes([
|
|
79
|
+
{
|
|
80
|
+
id: 'nav-2',
|
|
81
|
+
type: 'navigation-go-to-page',
|
|
82
|
+
config: {
|
|
83
|
+
pageId: '/products/[slug]',
|
|
84
|
+
targetPage: {
|
|
85
|
+
pageName: 'product-details',
|
|
86
|
+
staticUrl: '/products',
|
|
87
|
+
isDetailsPage: true,
|
|
88
|
+
rowOwnerTable: 'teleport_products',
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
])
|
|
93
|
+
expect(resolveOrderDetailsRoutePrefix(uidl)).toBe(DEFAULT_ORDER_DETAILS_PREFIX)
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('ignores a NON-details page that happens to be named order-details', () => {
|
|
97
|
+
const uidl = uidlWithNodes([
|
|
98
|
+
goToOrderDetails({ isDetailsPage: false, rowOwnerTable: undefined, staticUrl: '/nope' }),
|
|
99
|
+
])
|
|
100
|
+
expect(resolveOrderDetailsRoutePrefix(uidl)).toBe(DEFAULT_ORDER_DETAILS_PREFIX)
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it('also scans custom workflow nodes', () => {
|
|
104
|
+
const uidl = {
|
|
105
|
+
workflows: {
|
|
106
|
+
workflows: {},
|
|
107
|
+
customNodes: { checkout: { id: 'checkout', nodes: [goToOrderDetails()] } },
|
|
108
|
+
},
|
|
109
|
+
} as unknown as ProjectUIDL
|
|
110
|
+
expect(resolveOrderDetailsRoutePrefix(uidl)).toBe('/orders')
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('returns the default for a project with no workflows at all', () => {
|
|
114
|
+
expect(resolveOrderDetailsRoutePrefix({} as ProjectUIDL)).toBe(DEFAULT_ORDER_DETAILS_PREFIX)
|
|
115
|
+
expect(resolveOrderDetailsRoutePrefix({ workflows: {} } as unknown as ProjectUIDL)).toBe(
|
|
116
|
+
DEFAULT_ORDER_DETAILS_PREFIX
|
|
117
|
+
)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('survives malformed nodes', () => {
|
|
121
|
+
const uidl = uidlWithNodes([null, 'nope', { type: 'navigation-go-to-page' }, { config: {} }])
|
|
122
|
+
expect(resolveOrderDetailsRoutePrefix(uidl)).toBe(DEFAULT_ORDER_DETAILS_PREFIX)
|
|
123
|
+
})
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
describe('buildPaymentMetadataBuilder — successUrl uses the resolved prefix', () => {
|
|
127
|
+
const run = (prefix: string) => {
|
|
128
|
+
const code = buildPaymentMetadataBuilder(prefix)
|
|
129
|
+
const fn = new Function(code + '\nreturn customHandler;')() as (params: unknown[]) => {
|
|
130
|
+
successUrl: string
|
|
131
|
+
}
|
|
132
|
+
return fn([{ id: 'uuid-1', total_amount: 10 }, { result: 'ORD-1' }])
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
it('builds the redirect under the project route, not the hard-coded one', () => {
|
|
136
|
+
expect(run('/orders').successUrl).toBe('/orders/ORD-1?payment=success')
|
|
137
|
+
expect(run('/account/receipts').successUrl).toBe('/account/receipts/ORD-1?payment=success')
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it('encodes the order number so an exotic token can never break the URL', () => {
|
|
141
|
+
const code = buildPaymentMetadataBuilder('/orders')
|
|
142
|
+
const fn = new Function(code + '\nreturn customHandler;')() as (params: unknown[]) => {
|
|
143
|
+
successUrl: string
|
|
144
|
+
}
|
|
145
|
+
const result = fn([{ id: 'uuid-1', total_amount: 10, order_number: 'ORD 1/2' }])
|
|
146
|
+
expect(result.successUrl).toBe('/orders/ORD%201%2F2?payment=success')
|
|
147
|
+
})
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* ⛔ THE HOLE THE TWO SOURCES ABOVE STILL LEFT: both depend on finding a
|
|
152
|
+
* `navigation-go-to-page` node that targets the order-details page. A project
|
|
153
|
+
* whose checkout has no such node — an AI-authored one, or an online-payment-
|
|
154
|
+
* only flow — fell straight through to `/order-details`, the very 404 this file
|
|
155
|
+
* exists to prevent, while the correct route was sitting in the `successUrl` of
|
|
156
|
+
* the node about to be replaced.
|
|
157
|
+
*/
|
|
158
|
+
describe('readSuccessUrlPrefix — the URL the node itself already declares', () => {
|
|
159
|
+
const handler = (literal: string) =>
|
|
160
|
+
`function customHandler(params) {\n return {\n successUrl: ${literal} + encodeURIComponent(orderNumber) + '?payment=success',\n };\n}`
|
|
161
|
+
|
|
162
|
+
it('reads the prefix the GUI baked in, double- or single-quoted', () => {
|
|
163
|
+
expect(readSuccessUrlPrefix(handler('"/orders/"'))).toBe('/orders')
|
|
164
|
+
expect(readSuccessUrlPrefix(handler("'/shop/orders/'"))).toBe('/shop/orders')
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
it('reads a prefix with no trailing slash', () => {
|
|
168
|
+
expect(readSuccessUrlPrefix(handler('"/orders"'))).toBe('/orders')
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
it('refuses anything that is not a bare prefix', () => {
|
|
172
|
+
expect(readSuccessUrlPrefix(handler('"/orders/[order_number]/"'))).toBeNull()
|
|
173
|
+
expect(readSuccessUrlPrefix(handler('"/orders/?payment=success"'))).toBeNull()
|
|
174
|
+
expect(readSuccessUrlPrefix(handler('"orders/"'))).toBeNull()
|
|
175
|
+
// A bare root would build `successUrl: "//" + orderNumber` — a
|
|
176
|
+
// protocol-relative URL pointing at another host.
|
|
177
|
+
expect(readSuccessUrlPrefix(handler('"/"'))).toBeNull()
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
it('returns null when there is no successUrl literal to read', () => {
|
|
181
|
+
expect(readSuccessUrlPrefix('function customHandler(params) { return {}; }')).toBeNull()
|
|
182
|
+
expect(readSuccessUrlPrefix('')).toBeNull()
|
|
183
|
+
expect(readSuccessUrlPrefix(undefined as unknown as string)).toBeNull()
|
|
184
|
+
})
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
describe('the rewrite keeps the route the project or the node already knows', () => {
|
|
188
|
+
const AI_METADATA_NODE = (successUrl: string) => ({
|
|
189
|
+
id: 'meta-1',
|
|
190
|
+
type: 'general-custom-js',
|
|
191
|
+
config: {
|
|
192
|
+
code: `function customHandler(params) {
|
|
193
|
+
var order = params[14] || {};
|
|
194
|
+
return {
|
|
195
|
+
successUrl: '${successUrl}' + order.order_number + '?payment=success',
|
|
196
|
+
metadataJson: JSON.stringify({ orderId: order.id })
|
|
197
|
+
};
|
|
198
|
+
}`,
|
|
199
|
+
},
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
const successUrlOf = (uidl: ProjectUIDL): string => {
|
|
203
|
+
rewriteLowStockCustomHandlers(uidl)
|
|
204
|
+
const nodes = (
|
|
205
|
+
uidl.workflows as unknown as {
|
|
206
|
+
workflows: Record<string, { nodes: Array<{ config?: { code?: string } }> }>
|
|
207
|
+
}
|
|
208
|
+
).workflows.placeOrder.nodes
|
|
209
|
+
const rewritten = nodes.find((node) => (node.config?.code ?? '').includes('successUrl'))
|
|
210
|
+
const fn = new Function(rewritten!.config!.code + '\nreturn customHandler;')() as (
|
|
211
|
+
params: unknown[]
|
|
212
|
+
) => { successUrl: string }
|
|
213
|
+
return fn([{ id: 'uuid-1', total_amount: 10 }, { result: 'ORD-1' }]).successUrl
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
it('THE REPORTED DEFECT: the project route wins over the old hard-coded one', () => {
|
|
217
|
+
const uidl = uidlWithNodes([goToOrderDetails(), AI_METADATA_NODE('/order-details/')])
|
|
218
|
+
expect(successUrlOf(uidl)).toBe('/orders/ORD-1?payment=success')
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
it('falls back to the URL the node declares when the project has no navigation node', () => {
|
|
222
|
+
const uidl = uidlWithNodes([AI_METADATA_NODE('/orders/')])
|
|
223
|
+
expect(successUrlOf(uidl)).toBe('/orders/ORD-1?payment=success')
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
it('the PROJECT still wins when the two disagree — the paid path must match the COD path', () => {
|
|
227
|
+
const uidl = uidlWithNodes([
|
|
228
|
+
goToOrderDetails({ staticUrl: '/account/receipts' }),
|
|
229
|
+
AI_METADATA_NODE('/orders/'),
|
|
230
|
+
])
|
|
231
|
+
expect(successUrlOf(uidl)).toBe('/account/receipts/ORD-1?payment=success')
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
it('keeps the last-resort constant when neither source says anything', () => {
|
|
235
|
+
const uidl = uidlWithNodes([AI_METADATA_NODE('/')])
|
|
236
|
+
expect(successUrlOf(uidl)).toBe('/order-details/ORD-1?payment=success')
|
|
237
|
+
})
|
|
238
|
+
})
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { generateSharedRuntimeUtilsCode } from '../src/executor-generator'
|
|
2
|
+
import { generateServerSegmentAPIRoute } from '../src/api-route-generator'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Regression: every component-bodied transactional email shipped with its
|
|
6
|
+
* `{{token}}` merge fields unfilled.
|
|
7
|
+
*
|
|
8
|
+
* The fill was pasted at individual call sites instead of living in the one
|
|
9
|
+
* function every path shares. It reached only three of the sixteen sites — the
|
|
10
|
+
* client executor, and the main + loop-body loops of the cron/webhook route —
|
|
11
|
+
* so the MAIN loop of `generateServerSegmentAPIRoute`, where every ordinary
|
|
12
|
+
* page workflow's email node actually executes, never applied it. Delivered
|
|
13
|
+
* result: literal `{{customerName}}` / `{{orderNumber}}` in the body, the same
|
|
14
|
+
* in the SUBJECT line, an un-expanded `<!--tq:each products-->` row block, and
|
|
15
|
+
* `href="{{resetUrl}}"` on the password-reset button — an invalid relative URL
|
|
16
|
+
* that mail clients drop, so the button had no link at all.
|
|
17
|
+
*
|
|
18
|
+
* These tests EXECUTE the emitted runtime rather than grepping it: the defect
|
|
19
|
+
* was never a missing function, it was a function nobody called.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
type ResolveConfig = (
|
|
23
|
+
config: Record<string, unknown>,
|
|
24
|
+
context: Record<string, unknown>
|
|
25
|
+
) => Record<string, unknown>
|
|
26
|
+
|
|
27
|
+
function loadRuntime(): { resolveConfig: ResolveConfig } {
|
|
28
|
+
const code = generateSharedRuntimeUtilsCode()
|
|
29
|
+
const moduleShim = { exports: {} as Record<string, unknown> }
|
|
30
|
+
// eslint-disable-next-line no-new-func
|
|
31
|
+
new Function('module', 'exports', 'require', 'process', code)(
|
|
32
|
+
moduleShim,
|
|
33
|
+
moduleShim.exports,
|
|
34
|
+
() => ({}),
|
|
35
|
+
{ env: {} }
|
|
36
|
+
)
|
|
37
|
+
const exported = (moduleShim.exports as { resolveConfig?: ResolveConfig }).resolveConfig
|
|
38
|
+
if (typeof exported !== 'function') {
|
|
39
|
+
throw new Error('server-runtime does not export resolveConfig')
|
|
40
|
+
}
|
|
41
|
+
return { resolveConfig: exported }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const wfCtx = (nodeId: string, path: string[]) => ({
|
|
45
|
+
type: 'workflowContext',
|
|
46
|
+
nodeId,
|
|
47
|
+
path: [nodeId, ...path],
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
describe('resolveConfig fills component-bodied email templates', () => {
|
|
51
|
+
const { resolveConfig } = loadRuntime()
|
|
52
|
+
|
|
53
|
+
const context = {
|
|
54
|
+
assemble: {
|
|
55
|
+
customerName: 'Jane Cooper',
|
|
56
|
+
orderNumber: 'ORD-42',
|
|
57
|
+
products: [
|
|
58
|
+
{ product_name: 'Beans', quantity: '2', unit_price: '12.00', currency: 'USD' },
|
|
59
|
+
{ product_name: 'Mug', quantity: '1', unit_price: '15.00', currency: 'USD' },
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
buildRef: { result: 'WDR-7' },
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const emailConfig = (over: Record<string, unknown> = {}) => ({
|
|
66
|
+
to: 'jane@example.com',
|
|
67
|
+
subject: 'New withdrawal request {{requestReference}} for order {{orderNumber}}',
|
|
68
|
+
body: '<p>Submitted by {{customerName}} for order {{orderNumber}}.</p>',
|
|
69
|
+
templateParams: [
|
|
70
|
+
{ key: 'customerName', value: wfCtx('assemble', ['customerName']) },
|
|
71
|
+
{ key: 'orderNumber', value: wfCtx('assemble', ['orderNumber']) },
|
|
72
|
+
{ key: 'requestReference', value: wfCtx('buildRef', ['result']) },
|
|
73
|
+
{ key: 'products', value: wfCtx('assemble', ['products']) },
|
|
74
|
+
{ key: 'companyName', value: 'Acme Inc.' },
|
|
75
|
+
],
|
|
76
|
+
...over,
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('fills the BODY from workflow-context-bound params', () => {
|
|
80
|
+
const resolved = resolveConfig(emailConfig(), context)
|
|
81
|
+
expect(resolved.body).toBe('<p>Submitted by Jane Cooper for order ORD-42.</p>')
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('fills the SUBJECT too — the email title had the same defect', () => {
|
|
85
|
+
const resolved = resolveConfig(emailConfig(), context)
|
|
86
|
+
expect(resolved.subject).toBe('New withdrawal request WDR-7 for order ORD-42')
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('fills a literal (non-bound) param value', () => {
|
|
90
|
+
const resolved = resolveConfig(emailConfig({ body: '<p>Sent by {{companyName}}</p>' }), context)
|
|
91
|
+
expect(resolved.body).toBe('<p>Sent by Acme Inc.</p>')
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('expands the array-mapper row block before the flat fill', () => {
|
|
95
|
+
const resolved = resolveConfig(
|
|
96
|
+
emailConfig({
|
|
97
|
+
body: '<div><!--tq:each products--><li>{{quantity}}x {{product_name}} @ {{unit_price}} {{currency}}</li><!--/tq:each--></div>',
|
|
98
|
+
}),
|
|
99
|
+
context
|
|
100
|
+
)
|
|
101
|
+
expect(resolved.body).toBe(
|
|
102
|
+
'<div><li>2x Beans @ 12.00 USD</li><li>1x Mug @ 15.00 USD</li></div>'
|
|
103
|
+
)
|
|
104
|
+
expect(resolved.body).not.toContain('<!--tq:each')
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('produces a REAL href on a token-bound link', () => {
|
|
108
|
+
// The password-reset button serializes to href="{{resetUrl}}". Unfilled,
|
|
109
|
+
// that is a relative URL mail clients discard — the reported "the reset
|
|
110
|
+
// button has no link".
|
|
111
|
+
const resolved = resolveConfig(
|
|
112
|
+
{
|
|
113
|
+
subject: 'Password Reset Request',
|
|
114
|
+
body: '<a href="{{resetUrl}}">Reset password</a>',
|
|
115
|
+
templateParams: [{ key: 'resetUrl', value: wfCtx('buildLink', ['resetLink']) }],
|
|
116
|
+
},
|
|
117
|
+
{ buildLink: { resetLink: 'https://shop.example.com/reset?token=abc' } }
|
|
118
|
+
)
|
|
119
|
+
expect(resolved.body).toBe(
|
|
120
|
+
'<a href="https://shop.example.com/reset?token=abc">Reset password</a>'
|
|
121
|
+
)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it('leaves configs without templateParams untouched', () => {
|
|
125
|
+
const resolved = resolveConfig({ query: 'SELECT 1', body: 'not {{anything}} special' }, context)
|
|
126
|
+
expect(resolved.body).toBe('not {{anything}} special')
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
it('does not choke when body/subject are absent or non-string', () => {
|
|
130
|
+
const resolved = resolveConfig(
|
|
131
|
+
{ templateParams: [{ key: 'a', value: 'b' }], to: ['x@y.z'] },
|
|
132
|
+
context
|
|
133
|
+
)
|
|
134
|
+
expect(resolved.to).toEqual(['x@y.z'])
|
|
135
|
+
})
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
describe('the server-segment route reaches the fill', () => {
|
|
139
|
+
// The route that runs every ordinary page workflow's server nodes. It had no
|
|
140
|
+
// fill of its own; it must now get one purely by calling resolveConfig.
|
|
141
|
+
const route = generateServerSegmentAPIRoute(
|
|
142
|
+
{
|
|
143
|
+
id: 'server-1',
|
|
144
|
+
env: 'server',
|
|
145
|
+
nodeIds: ['n1'],
|
|
146
|
+
nodes: [
|
|
147
|
+
{
|
|
148
|
+
id: 'n1',
|
|
149
|
+
type: 'email-resend',
|
|
150
|
+
label: 'Send Withdrawal Confirmation Email',
|
|
151
|
+
stepNumber: 1,
|
|
152
|
+
config: {
|
|
153
|
+
to: 'a@b.c',
|
|
154
|
+
subject: 'Ref {{requestReference}}',
|
|
155
|
+
body: '<p>{{customerName}}</p>',
|
|
156
|
+
templateParams: [{ key: 'customerName', value: 'Jane' }],
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
edges: [],
|
|
161
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
162
|
+
} as any,
|
|
163
|
+
'Submit Withdrawal'
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
it('routes the node config through the shared resolveConfig', () => {
|
|
167
|
+
expect(route).toContain('const resolveConfig = utils.resolveConfig')
|
|
168
|
+
expect(route).toContain('resolveConfig(node.config, context)')
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
it('carries no private copy of the fill that could drift', () => {
|
|
172
|
+
expect(route).not.toContain('applyTemplateParams')
|
|
173
|
+
})
|
|
174
|
+
})
|
|
@@ -11,12 +11,25 @@ import { utilityParseUrl } from '../src/nodes/utility/utility-parse-url'
|
|
|
11
11
|
import { utilityFormatPhoneNumber } from '../src/nodes/utility/utility-format-phone-number'
|
|
12
12
|
import { utilitySemanticSearch } from '../src/nodes/utility/utility-semantic-search'
|
|
13
13
|
import { utilityHybridSearch } from '../src/nodes/utility/utility-hybrid-search'
|
|
14
|
+
import { resolveHandlerEntryName } from '../src/nodes/types'
|
|
14
15
|
|
|
15
16
|
/* tslint:disable:no-eval */
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
// Loads a handler the way the generated project does — the source goes inside
|
|
18
|
+
// an IIFE that returns the entry point by name (see `api-route-generator`).
|
|
19
|
+
//
|
|
20
|
+
// ⛔ The previous `eval('(' + source + ')')` assumed a handler emits nothing
|
|
21
|
+
// but a bare function expression. That stopped being true the moment a handler
|
|
22
|
+
// needed a shared prelude: the AI-provider utils emit `var` helpers ahead of
|
|
23
|
+
// the entry, exactly as `ai-custom-prompt` has always done, and the wrapper
|
|
24
|
+
// then failed with "Unexpected token 'var'".
|
|
25
|
+
//
|
|
26
|
+
// `eval` rather than `new Function` on purpose: it keeps module scope, so
|
|
27
|
+
// handlers that `require('crypto')` and the ts-jest `__awaiter`/`__generator`
|
|
28
|
+
// helpers both still resolve.
|
|
29
|
+
function createHandler(generator: { nodeType: string; generateHandler(): string }) {
|
|
30
|
+
const source = generator.generateHandler().trim()
|
|
31
|
+
const entry = resolveHandlerEntryName(source, generator.nodeType)
|
|
32
|
+
return eval(`(function () {\n${source}\nreturn ${entry};\n})()`)
|
|
20
33
|
}
|
|
21
34
|
|
|
22
35
|
describe('utility-csv-parse', () => {
|
|
@@ -567,7 +580,7 @@ describe('utility-semantic-search', () => {
|
|
|
567
580
|
{ title: 'Python guide', content: 'Python for beginners' },
|
|
568
581
|
{ title: 'Advanced JavaScript', content: 'Deep dive into JS patterns' },
|
|
569
582
|
]
|
|
570
|
-
const result = await handler({ query: 'JavaScript', collection }, {})
|
|
583
|
+
const result = await handler({ query: 'JavaScript', documents: collection }, {})
|
|
571
584
|
expect(result.results.length).toBeGreaterThan(0)
|
|
572
585
|
expect(result.results[0].item.title).toContain('JavaScript')
|
|
573
586
|
})
|
|
@@ -578,7 +591,7 @@ describe('utility-semantic-search', () => {
|
|
|
578
591
|
{ title: 'B', embedding: [0, 1, 0] },
|
|
579
592
|
{ title: 'C', embedding: [0.9, 0.1, 0] },
|
|
580
593
|
]
|
|
581
|
-
const result = await handler({ queryEmbedding: [1, 0, 0], collection }, {})
|
|
594
|
+
const result = await handler({ queryEmbedding: [1, 0, 0], documents: collection }, {})
|
|
582
595
|
expect(result.results.length).toBeGreaterThan(0)
|
|
583
596
|
expect(result.results[0].item.title).toBe('A')
|
|
584
597
|
})
|
|
@@ -589,12 +602,12 @@ describe('utility-semantic-search', () => {
|
|
|
589
602
|
{ title: 'B', embedding: [0.9, 0.1, 0] },
|
|
590
603
|
{ title: 'C', embedding: [0.8, 0.2, 0] },
|
|
591
604
|
]
|
|
592
|
-
const result = await handler({ queryEmbedding: [1, 0, 0], collection, topK: 2 }, {})
|
|
605
|
+
const result = await handler({ queryEmbedding: [1, 0, 0], documents: collection, topK: 2 }, {})
|
|
593
606
|
expect(result.results.length).toBe(2)
|
|
594
607
|
})
|
|
595
608
|
|
|
596
609
|
it('returns error for empty inputs', async () => {
|
|
597
|
-
const result = await handler({ query: '',
|
|
610
|
+
const result = await handler({ query: '', documents: [] }, {})
|
|
598
611
|
expect(result.error).toBeTruthy()
|
|
599
612
|
})
|
|
600
613
|
})
|
|
@@ -612,7 +625,7 @@ describe('utility-hybrid-search', () => {
|
|
|
612
625
|
{ title: 'JS patterns', embedding: [0.8, 0.2, 0] },
|
|
613
626
|
]
|
|
614
627
|
const result = await handler(
|
|
615
|
-
{ query: 'JavaScript', queryEmbedding: [1, 0, 0], collection, fields: ['title'] },
|
|
628
|
+
{ query: 'JavaScript', queryEmbedding: [1, 0, 0], documents: collection, fields: ['title'] },
|
|
616
629
|
{}
|
|
617
630
|
)
|
|
618
631
|
expect(result.results.length).toBeGreaterThan(0)
|
|
@@ -621,13 +634,16 @@ describe('utility-hybrid-search', () => {
|
|
|
621
634
|
|
|
622
635
|
it('works with text-only (no embeddings)', async () => {
|
|
623
636
|
const collection = [{ title: 'JavaScript basics' }, { title: 'Python guide' }]
|
|
624
|
-
const result = await handler(
|
|
637
|
+
const result = await handler(
|
|
638
|
+
{ query: 'JavaScript', documents: collection, fields: ['title'] },
|
|
639
|
+
{}
|
|
640
|
+
)
|
|
625
641
|
expect(result.results.length).toBe(1)
|
|
626
642
|
expect(result.hasSemanticScores).toBe(false)
|
|
627
643
|
})
|
|
628
644
|
|
|
629
645
|
it('returns error for empty query', async () => {
|
|
630
|
-
const result = await handler({ query: '',
|
|
646
|
+
const result = await handler({ query: '', documents: [] }, {})
|
|
631
647
|
expect(result.error).toBeTruthy()
|
|
632
648
|
})
|
|
633
649
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-route-generator.d.ts","sourceRoot":"","sources":["../../src/api-route-generator.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EAIZ,sBAAsB,EACtB,sBAAsB,EACvB,MAAM,4BAA4B,CAAA;AACnC,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAqBzC,eAAO,MAAM,6BAA6B,YAC/B,eAAe,iBACT,MAAM,eACR,sBAAsB,KAClC,
|
|
1
|
+
{"version":3,"file":"api-route-generator.d.ts","sourceRoot":"","sources":["../../src/api-route-generator.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EAIZ,sBAAsB,EACtB,sBAAsB,EACvB,MAAM,4BAA4B,CAAA;AACnC,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAqBzC,eAAO,MAAM,6BAA6B,YAC/B,eAAe,iBACT,MAAM,eACR,sBAAsB,KAClC,MAsXF,CAAA;AAED,eAAO,MAAM,kBAAkB,YAAa,eAAe,KAAG,OAE7D,CAAA;AAED,eAAO,MAAM,sCAAsC,YACxC,eAAe,iBACT,MAAM,eACR,sBAAsB,KAClC,MAgcF,CAAA;AA0HD,eAAO,MAAM,oBAAoB,aACrB,YAAY,gBACR,OAAO,MAAM,EAAE,sBAAsB,CAAC,KACnD,MAuFF,CAAA;AAED,eAAO,MAAM,mBAAmB,eAClB,MAAM,aACP,MAAM,iBACF,MAAM,KACpB,MAuBF,CAAA;AAED,eAAO,MAAM,oBAAoB,aAAc,YAAY,KAAG,MAa7D,CAAA;AAoRD,eAAO,MAAM,+BAA+B,aAChC,YAAY,gBACR,OAAO,MAAM,EAAE,sBAAsB,CAAC,KACnD,MA0MF,CAAA;AAED,eAAO,MAAM,uBAAuB,aAAc,YAAY,KAAG,MAiBhE,CAAA;AAED,eAAO,MAAM,mBAAmB,aAAc,YAAY,KAAG,MAAM,EAqBlE,CAAA"}
|
|
@@ -82,7 +82,11 @@ module.exports = async function handler(req, res) {
|
|
|
82
82
|
// are drained by the settle below rather than being lost when we reply.
|
|
83
83
|
context.__pendingNodePromises = [];${requestInjection}
|
|
84
84
|
var __proto = req.headers['x-forwarded-proto'] || (req.headers.host && (req.headers.host.startsWith('localhost') || req.headers.host.startsWith('127.0.0.1')) ? 'http' : 'https');
|
|
85
|
-
context.__baseUrl = __proto + '://' + req.headers.host
|
|
85
|
+
context.__baseUrl = __proto + '://' + req.headers.host;
|
|
86
|
+
// Credentials for this deployment's calls to its own /api/data routes —
|
|
87
|
+
// without them a protected deployment 401s itself and every data node
|
|
88
|
+
// returns no rows. See internalRequestHeaders in runtime-utils.
|
|
89
|
+
context.__internalHeaders = utils.internalRequestHeaders(req);${auth.guardCall}
|
|
86
90
|
const sortedNodes = SEGMENT_CONFIG.nodes.slice().sort(function(a, b) { return a.stepNumber - b.stepNumber; });
|
|
87
91
|
|
|
88
92
|
for (let i = 0; i < sortedNodes.length; i++) {
|
|
@@ -184,10 +188,6 @@ module.exports = async function handler(req, res) {
|
|
|
184
188
|
var bNode = bodyNodes[bi];
|
|
185
189
|
var bResolved = resolveConfig(bNode.config, context);
|
|
186
190
|
bResolved.__nodeId = bNode.id;
|
|
187
|
-
if (bResolved && Array.isArray(bResolved.templateParams)) {
|
|
188
|
-
if (typeof bResolved.body === 'string') { bResolved.body = utils.applyTemplateParams(bResolved.body, bResolved.templateParams); }
|
|
189
|
-
if (typeof bResolved.subject === 'string') { bResolved.subject = utils.applyTemplateParams(bResolved.subject, bResolved.templateParams); }
|
|
190
|
-
}
|
|
191
191
|
if (bNode.type === 'general-if-statement') {
|
|
192
192
|
context[bNode.id] = { result: utils.evaluateCondition(bResolved, context) };
|
|
193
193
|
continue;
|
|
@@ -368,6 +368,12 @@ module.exports = async function handler(req, res) {
|
|
|
368
368
|
await utils.settlePendingNodePromises(context);
|
|
369
369
|
delete context.__request;
|
|
370
370
|
delete context.__pendingNodePromises;
|
|
371
|
+
// MUST be stripped before replying: the results payload below IS this
|
|
372
|
+
// context, serialized straight to the browser. __internalHeaders carries
|
|
373
|
+
// the visitor's forwarded cookie, and writing it into a JS-readable
|
|
374
|
+
// response body would hand the session token to any script on the page,
|
|
375
|
+
// undoing the httpOnly flag it was set with.
|
|
376
|
+
delete context.__internalHeaders;
|
|
371
377
|
res.status(200).json({ success: true, results: context });
|
|
372
378
|
} catch (error) {
|
|
373
379
|
console.error('Workflow segment error:', error);
|
|
@@ -472,7 +478,11 @@ module.exports = async function handler(req, res) {
|
|
|
472
478
|
// See the non-streaming segment route — shared queue for nested custom nodes.
|
|
473
479
|
context.__pendingNodePromises = [];${requestInjection}
|
|
474
480
|
var __proto = req.headers['x-forwarded-proto'] || (req.headers.host && (req.headers.host.startsWith('localhost') || req.headers.host.startsWith('127.0.0.1')) ? 'http' : 'https');
|
|
475
|
-
context.__baseUrl = __proto + '://' + req.headers.host
|
|
481
|
+
context.__baseUrl = __proto + '://' + req.headers.host;
|
|
482
|
+
// Credentials for this deployment's calls to its own /api/data routes —
|
|
483
|
+
// without them a protected deployment 401s itself and every data node
|
|
484
|
+
// returns no rows. See internalRequestHeaders in runtime-utils.
|
|
485
|
+
context.__internalHeaders = utils.internalRequestHeaders(req);${auth.guardCall}
|
|
476
486
|
const sortedNodes = SEGMENT_CONFIG.nodes.slice().sort(function(a, b) { return a.stepNumber - b.stepNumber; });
|
|
477
487
|
const executed = {};
|
|
478
488
|
|
|
@@ -787,6 +797,12 @@ module.exports = async function handler(req, res) {
|
|
|
787
797
|
await utils.settlePendingNodePromises(context);
|
|
788
798
|
delete context.__request;
|
|
789
799
|
delete context.__pendingNodePromises;
|
|
800
|
+
// MUST be stripped before replying: the results payload below IS this
|
|
801
|
+
// context, serialized straight to the browser. __internalHeaders carries
|
|
802
|
+
// the visitor's forwarded cookie, and writing it into a JS-readable
|
|
803
|
+
// response body would hand the session token to any script on the page,
|
|
804
|
+
// undoing the httpOnly flag it was set with.
|
|
805
|
+
delete context.__internalHeaders;
|
|
790
806
|
if (streamStarted) {
|
|
791
807
|
res.write('data: ' + JSON.stringify({ type: 'done', success: true, results: context }) + '\\n\\n');
|
|
792
808
|
res.end();
|
|
@@ -1069,10 +1085,6 @@ const generateNodeExecutionLoop = (configVar, options) => {
|
|
|
1069
1085
|
var __configError = utils.finalizeResolvedConfig(node.type, resolved);
|
|
1070
1086
|
if (__configError) { throw new Error(__configError); }
|
|
1071
1087
|
resolved.__nodeId = node.id;
|
|
1072
|
-
if (resolved && Array.isArray(resolved.templateParams)) {
|
|
1073
|
-
if (typeof resolved.body === 'string') { resolved.body = utils.applyTemplateParams(resolved.body, resolved.templateParams); }
|
|
1074
|
-
if (typeof resolved.subject === 'string') { resolved.subject = utils.applyTemplateParams(resolved.subject, resolved.templateParams); }
|
|
1075
|
-
}
|
|
1076
1088
|
|
|
1077
1089
|
if (node.type === 'general-if-statement') {
|
|
1078
1090
|
var condResult = utils.evaluateCondition(resolved, context);
|
|
@@ -1143,10 +1155,6 @@ const generateNodeExecutionLoop = (configVar, options) => {
|
|
|
1143
1155
|
var bNode = bodyNodes[bi];
|
|
1144
1156
|
var bResolved = resolveConfig(bNode.config, context);
|
|
1145
1157
|
bResolved.__nodeId = bNode.id;
|
|
1146
|
-
if (bResolved && Array.isArray(bResolved.templateParams)) {
|
|
1147
|
-
if (typeof bResolved.body === 'string') { bResolved.body = utils.applyTemplateParams(bResolved.body, bResolved.templateParams); }
|
|
1148
|
-
if (typeof bResolved.subject === 'string') { bResolved.subject = utils.applyTemplateParams(bResolved.subject, bResolved.templateParams); }
|
|
1149
|
-
}
|
|
1150
1158
|
if (bNode.type === 'general-if-statement') {
|
|
1151
1159
|
context[bNode.id] = { result: utils.evaluateCondition(bResolved, context) };
|
|
1152
1160
|
continue;
|
|
@@ -1448,7 +1456,11 @@ ${generateSignatureVerificationBlock(webhookConfig)}
|
|
|
1448
1456
|
context.__pendingNodePromises = [];
|
|
1449
1457
|
context[WORKFLOW_CONFIG.triggerNodeId] = triggerContext;
|
|
1450
1458
|
var __proto = req.headers['x-forwarded-proto'] || (req.headers.host && (req.headers.host.startsWith('localhost') || req.headers.host.startsWith('127.0.0.1')) ? 'http' : 'https');
|
|
1451
|
-
context.__baseUrl = __proto + '://' + req.headers.host
|
|
1459
|
+
context.__baseUrl = __proto + '://' + req.headers.host;
|
|
1460
|
+
// Credentials for this deployment's calls to its own /api/data routes —
|
|
1461
|
+
// without them a protected deployment 401s itself and every data node
|
|
1462
|
+
// returns no rows. See internalRequestHeaders in runtime-utils.
|
|
1463
|
+
context.__internalHeaders = utils.internalRequestHeaders(req);${requestInjection}
|
|
1452
1464
|
|
|
1453
1465
|
${executionLoop}
|
|
1454
1466
|
|