@teleporthq/teleport-plugin-next-workflows 0.43.62 → 0.43.63
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__/data-update-item-runtime.test.ts +39 -0
- package/__tests__/page-revalidate-node.test.ts +276 -0
- package/__tests__/payment-refund.test.ts +135 -0
- package/dist/cjs/nodes/analytics/commerce-tracking.d.ts +46 -0
- package/dist/cjs/nodes/analytics/commerce-tracking.d.ts.map +1 -0
- package/dist/cjs/nodes/analytics/commerce-tracking.js +94 -0
- package/dist/cjs/nodes/analytics/commerce-tracking.js.map +1 -0
- package/dist/cjs/nodes/cache/page-revalidate.d.ts +3 -0
- package/dist/cjs/nodes/cache/page-revalidate.d.ts.map +1 -0
- package/dist/cjs/nodes/cache/page-revalidate.js +125 -0
- package/dist/cjs/nodes/cache/page-revalidate.js.map +1 -0
- package/dist/cjs/nodes/cart/cart-add-item.d.ts.map +1 -1
- package/dist/cjs/nodes/cart/cart-add-item.js +22 -1
- package/dist/cjs/nodes/cart/cart-add-item.js.map +1 -1
- package/dist/cjs/nodes/cart/cart-remove-item.d.ts.map +1 -1
- package/dist/cjs/nodes/cart/cart-remove-item.js +23 -1
- package/dist/cjs/nodes/cart/cart-remove-item.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 +42 -1
- package/dist/cjs/nodes/data/data-create-item.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 +38 -3
- package/dist/cjs/nodes/data/data-update-item.js.map +1 -1
- package/dist/cjs/nodes/index.d.ts.map +1 -1
- package/dist/cjs/nodes/index.js +3 -1
- package/dist/cjs/nodes/index.js.map +1 -1
- package/dist/cjs/nodes/payment/payment-refund.d.ts +3 -0
- package/dist/cjs/nodes/payment/payment-refund.d.ts.map +1 -0
- package/dist/cjs/nodes/payment/payment-refund.js +446 -0
- package/dist/cjs/nodes/payment/payment-refund.js.map +1 -0
- package/dist/cjs/secret-collector.d.ts.map +1 -1
- package/dist/cjs/secret-collector.js +3 -0
- package/dist/cjs/secret-collector.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/nodes/analytics/commerce-tracking.d.ts +46 -0
- package/dist/esm/nodes/analytics/commerce-tracking.d.ts.map +1 -0
- package/dist/esm/nodes/analytics/commerce-tracking.js +90 -0
- package/dist/esm/nodes/analytics/commerce-tracking.js.map +1 -0
- package/dist/esm/nodes/cache/page-revalidate.d.ts +3 -0
- package/dist/esm/nodes/cache/page-revalidate.d.ts.map +1 -0
- package/dist/esm/nodes/cache/page-revalidate.js +122 -0
- package/dist/esm/nodes/cache/page-revalidate.js.map +1 -0
- package/dist/esm/nodes/cart/cart-add-item.d.ts.map +1 -1
- package/dist/esm/nodes/cart/cart-add-item.js +22 -1
- package/dist/esm/nodes/cart/cart-add-item.js.map +1 -1
- package/dist/esm/nodes/cart/cart-remove-item.d.ts.map +1 -1
- package/dist/esm/nodes/cart/cart-remove-item.js +23 -1
- package/dist/esm/nodes/cart/cart-remove-item.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 +42 -1
- package/dist/esm/nodes/data/data-create-item.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 +38 -3
- package/dist/esm/nodes/data/data-update-item.js.map +1 -1
- package/dist/esm/nodes/index.d.ts.map +1 -1
- package/dist/esm/nodes/index.js +3 -1
- package/dist/esm/nodes/index.js.map +1 -1
- package/dist/esm/nodes/payment/payment-refund.d.ts +3 -0
- package/dist/esm/nodes/payment/payment-refund.d.ts.map +1 -0
- package/dist/esm/nodes/payment/payment-refund.js +443 -0
- package/dist/esm/nodes/payment/payment-refund.js.map +1 -0
- package/dist/esm/secret-collector.d.ts.map +1 -1
- package/dist/esm/secret-collector.js +3 -0
- package/dist/esm/secret-collector.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/src/nodes/analytics/commerce-tracking.ts +93 -0
- package/src/nodes/cache/page-revalidate.ts +140 -0
- package/src/nodes/cart/cart-add-item.ts +35 -1
- package/src/nodes/cart/cart-remove-item.ts +36 -1
- package/src/nodes/data/data-create-item.ts +44 -1
- package/src/nodes/data/data-update-item.ts +39 -3
- package/src/nodes/index.ts +4 -0
- package/src/nodes/payment/payment-refund.ts +512 -0
- package/src/secret-collector.ts +3 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@teleporthq/teleport-plugin-next-workflows",
|
|
3
|
-
"version": "0.43.
|
|
3
|
+
"version": "0.43.63",
|
|
4
4
|
"description": "A plugin for workflow code generation in Next.js projects.",
|
|
5
5
|
"author": "teleportHQ",
|
|
6
6
|
"license": "MIT",
|
|
@@ -66,8 +66,8 @@
|
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
68
|
"@babel/types": "^7.5.5",
|
|
69
|
-
"@teleporthq/teleport-shared": "^0.43.
|
|
70
|
-
"@teleporthq/teleport-types": "^0.43.
|
|
69
|
+
"@teleporthq/teleport-shared": "^0.43.63",
|
|
70
|
+
"@teleporthq/teleport-types": "^0.43.63"
|
|
71
71
|
},
|
|
72
|
-
"gitHead": "
|
|
72
|
+
"gitHead": "2c7a1001dd77ae05f54ff41f44acfa9f89a156b4"
|
|
73
73
|
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one way a generated workflow handler reports a commerce funnel step.
|
|
3
|
+
*
|
|
4
|
+
* ## Why a global and not an import
|
|
5
|
+
*
|
|
6
|
+
* A node handler ships as a SERIALIZED function body (`fn.toString()`) that is
|
|
7
|
+
* evaluated at runtime with no module scope — it cannot import anything. The
|
|
8
|
+
* analytics tracker publishes `window.tpTrackCommerce` when it boots, and that
|
|
9
|
+
* global is the only channel a handler can reach.
|
|
10
|
+
*
|
|
11
|
+
* ## Why every call is guarded twice
|
|
12
|
+
*
|
|
13
|
+
* The global is absent on any project that did not enable analytics, and absent
|
|
14
|
+
* on every server-side render. These handlers run inside add-to-cart and
|
|
15
|
+
* checkout, where an exception costs a sale — so a missing tracker has to be a
|
|
16
|
+
* silent no-op, never a `ReferenceError`. The `try` covers the second failure
|
|
17
|
+
* mode: a tracker that is present but throws (storage disabled, a browser
|
|
18
|
+
* extension replacing the global) must not take the cart down with it.
|
|
19
|
+
*
|
|
20
|
+
* ## ⛔ Why a calling node must NOT import `trackCommerceStep`
|
|
21
|
+
*
|
|
22
|
+
* TypeScript's CommonJS emit rewrites a cross-module call into
|
|
23
|
+
* `(0, commerce_tracking_1.trackCommerceStep)(...)`. That survives `.toString()`
|
|
24
|
+
* verbatim, and `commerce_tracking_1` does not exist in the generated workflow
|
|
25
|
+
* segment — so the handler throws `ReferenceError` the first time a shopper adds
|
|
26
|
+
* to the cart, in production only, because nothing about it is visible in the
|
|
27
|
+
* TypeScript source.
|
|
28
|
+
*
|
|
29
|
+
* A caller therefore declares the name AMBIENTLY (`declare function
|
|
30
|
+
* trackCommerceStep`), which emits nothing and leaves a bare identifier in the
|
|
31
|
+
* serialized body, and appends `COMMERCE_TRACKING_HELPER_SOURCE` so the runtime
|
|
32
|
+
* definition travels with it. `assertHandlerHasNoModuleRefs` fails the build if
|
|
33
|
+
* a module reference ever creeps back in.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* ⛔ EXACTLY ONE PARAMETER. Not a style choice.
|
|
38
|
+
*
|
|
39
|
+
* When this package is minified by a consumer, the handler's own entry function
|
|
40
|
+
* loses its conventional name, and `resolveHandlerEntryName` then identifies the
|
|
41
|
+
* entry by ARITY: it prefers a 2-param declaration, because every real handler
|
|
42
|
+
* entry is `(config, context)`. A 2-param helper concatenated after a 1-param
|
|
43
|
+
* entry therefore WINS, and the generated workflow calls the tracking helper
|
|
44
|
+
* where it meant to call add-to-cart.
|
|
45
|
+
*
|
|
46
|
+
* One object parameter keeps the helper out of that contest, and the resolver's
|
|
47
|
+
* positional fallback then correctly picks the entry, which is emitted first.
|
|
48
|
+
* `resolve-handler-entry-name.test.ts` checks every registered node type against
|
|
49
|
+
* exactly this, and is what caught it.
|
|
50
|
+
*
|
|
51
|
+
* Not exported either: exporting invites the cross-module call this file exists
|
|
52
|
+
* to prevent. It is here to be read by `.toString()` below, nothing else.
|
|
53
|
+
*/
|
|
54
|
+
function trackCommerceStep(step: { name: string; detail?: Record<string, unknown> }): void {
|
|
55
|
+
try {
|
|
56
|
+
const tracker = (globalThis as Record<string, unknown>).tpTrackCommerce
|
|
57
|
+
if (typeof tracker === 'function') {
|
|
58
|
+
;(tracker as (n: string, d?: Record<string, unknown>) => void)(step.name, step.detail)
|
|
59
|
+
}
|
|
60
|
+
} catch (e) {
|
|
61
|
+
/* analytics must never break a cart or a checkout */
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** ES5 source of the helper, for concatenating after a serialized handler. */
|
|
66
|
+
export const COMMERCE_TRACKING_HELPER_SOURCE = trackCommerceStep.toString()
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Matches the two shapes TypeScript's CJS emit produces for a cross-module
|
|
70
|
+
* call — `(0, ns_1.fn)(…)` and a bare `ns_1.fn(…)`.
|
|
71
|
+
*/
|
|
72
|
+
const MODULE_REFERENCE = /\b[A-Za-z_$][\w$]*_\d+\s*\./
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Fails the BUILD when a serialized handler still references a module namespace.
|
|
76
|
+
*
|
|
77
|
+
* Cheap insurance against a failure that is otherwise invisible: the TypeScript
|
|
78
|
+
* compiles, the tests that only read the source pass, and the handler throws in
|
|
79
|
+
* a shopper's browser. Called by every `generateHandler` that concatenates a
|
|
80
|
+
* helper.
|
|
81
|
+
*/
|
|
82
|
+
export function assertHandlerHasNoModuleRefs(source: string, nodeType: string): string {
|
|
83
|
+
const match = source.match(MODULE_REFERENCE)
|
|
84
|
+
if (match) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
`The "${nodeType}" handler references the module namespace "${match[0]}". ` +
|
|
87
|
+
'A serialized handler has no module scope, so this throws ReferenceError at ' +
|
|
88
|
+
'runtime. Declare the helper ambiently (declare function ...) and append its ' +
|
|
89
|
+
'source instead of importing it.'
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
return source
|
|
93
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { NodeHandlerGenerator, handlerToString } from '../types'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Rebuilds statically generated pages on the published site.
|
|
5
|
+
*
|
|
6
|
+
* This is the OTHER cache a published project serves from. `cache-invalidate`
|
|
7
|
+
* clears the data-API cache; a page built by `getStaticProps` keeps serving the
|
|
8
|
+
* HTML it was built with regardless, until its ISR window expires. So an admin
|
|
9
|
+
* who creates, edits or deletes a row watches the public URL stay wrong for the
|
|
10
|
+
* length of that window. Calling `res.revalidate(path)` re-runs `getStaticProps`
|
|
11
|
+
* for that path immediately.
|
|
12
|
+
*
|
|
13
|
+
* SERVER-classified deliberately, for the same two reasons as
|
|
14
|
+
* `cache-invalidate`: `res.revalidate` only exists inside a server route, and
|
|
15
|
+
* being server-side lands it in the SAME segment as the write it follows, so
|
|
16
|
+
* adding it to a CRUD workflow costs no extra round trip.
|
|
17
|
+
*
|
|
18
|
+
* It reaches the rebuild through the app's own `POST /api/tq-revalidate` rather
|
|
19
|
+
* than by importing anything: node handlers ship as runtime source and are
|
|
20
|
+
* re-bundled by webpack, which rewrites `require(` — so an internal HTTP call is
|
|
21
|
+
* the sanctioned way for a handler to reach server-side machinery, and it is
|
|
22
|
+
* also the only way to get at a `res` object with `revalidate` on it.
|
|
23
|
+
*
|
|
24
|
+
* ⛔ Never mark this node fire-and-forget. Its workflows navigate or refresh a
|
|
25
|
+
* page immediately afterwards, and that reload routinely wins the race against
|
|
26
|
+
* an unawaited rebuild — leaving the visitor looking at the stale page this was
|
|
27
|
+
* meant to replace.
|
|
28
|
+
*
|
|
29
|
+
* ⛔ It also must NEVER return `{ success: false }` or a string `error`. The
|
|
30
|
+
* runtime treats both as FATAL and halts the workflow (`isFatalNodeResult`), so
|
|
31
|
+
* a page that could not be rebuilt would abort an admin's save AFTER the row was
|
|
32
|
+
* already written — no success toast, no navigation, and an error for an action
|
|
33
|
+
* that worked. Outcomes are reported as `revalidated` plus a `warning`, and the
|
|
34
|
+
* page's own ISR window remains the safety net.
|
|
35
|
+
*/
|
|
36
|
+
async function page_revalidate(config: any, context: Record<string, unknown>) {
|
|
37
|
+
const configured = Array.isArray(config.paths)
|
|
38
|
+
? config.paths
|
|
39
|
+
: config.paths === undefined || config.paths === null
|
|
40
|
+
? []
|
|
41
|
+
: [config.paths]
|
|
42
|
+
|
|
43
|
+
// One level of flattening: a step that revalidates several rows at once (a
|
|
44
|
+
// bulk delete, say) resolves ONE reference to an array of slugs rather than
|
|
45
|
+
// configuring a path per row.
|
|
46
|
+
const flattened: unknown[] = []
|
|
47
|
+
for (let i = 0; i < configured.length; i++) {
|
|
48
|
+
const entry = configured[i]
|
|
49
|
+
if (Array.isArray(entry)) {
|
|
50
|
+
for (let j = 0; j < entry.length; j++) {
|
|
51
|
+
flattened.push(entry[j])
|
|
52
|
+
}
|
|
53
|
+
continue
|
|
54
|
+
}
|
|
55
|
+
flattened.push(entry)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const paths: string[] = []
|
|
59
|
+
for (let i = 0; i < flattened.length; i++) {
|
|
60
|
+
const raw = flattened[i]
|
|
61
|
+
if (raw === undefined || raw === null || typeof raw === 'object') {
|
|
62
|
+
continue
|
|
63
|
+
}
|
|
64
|
+
const trimmed = String(raw).trim()
|
|
65
|
+
if (!trimmed) {
|
|
66
|
+
continue
|
|
67
|
+
}
|
|
68
|
+
// `res.revalidate` only accepts a site-root-relative path, and a step whose
|
|
69
|
+
// path came from a database column often holds the bare slug.
|
|
70
|
+
const normalized = trimmed.charAt(0) === '/' ? trimmed : '/' + trimmed
|
|
71
|
+
if (paths.indexOf(normalized) === -1) {
|
|
72
|
+
paths.push(normalized)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (!paths.length) {
|
|
77
|
+
return {
|
|
78
|
+
revalidated: [],
|
|
79
|
+
warning: 'No page paths were configured on this step.',
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const baseUrl = (context.__baseUrl as string) || ''
|
|
84
|
+
const __internalHeaders = (context.__internalHeaders as Record<string, string>) || {}
|
|
85
|
+
const __env = (globalThis as any).process && (globalThis as any).process.env
|
|
86
|
+
const secret = __env ? __env.TQ_CACHE_SECRET : ''
|
|
87
|
+
|
|
88
|
+
if (!secret) {
|
|
89
|
+
// Reported rather than thrown: the write this follows already succeeded, and
|
|
90
|
+
// the page still refreshes on its own revalidate window.
|
|
91
|
+
return {
|
|
92
|
+
revalidated: [],
|
|
93
|
+
warning:
|
|
94
|
+
'TQ_CACHE_SECRET is not configured, so published pages will refresh on their own schedule.',
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
const response = await fetch(baseUrl + '/api/tq-revalidate', {
|
|
100
|
+
method: 'POST',
|
|
101
|
+
headers: {
|
|
102
|
+
'Content-Type': 'application/json',
|
|
103
|
+
'x-tq-cache-secret': secret,
|
|
104
|
+
...__internalHeaders,
|
|
105
|
+
},
|
|
106
|
+
body: JSON.stringify({ paths }),
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
const data = await response.json()
|
|
110
|
+
|
|
111
|
+
if (!response.ok || !data.ok) {
|
|
112
|
+
const warning: string =
|
|
113
|
+
data.error || 'The published pages could not be refreshed; they will refresh on their own.'
|
|
114
|
+
return { revalidated: [], warning }
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const revalidated = Array.isArray(data.revalidated) ? data.revalidated : []
|
|
118
|
+
const failed = Array.isArray(data.failed) ? data.failed : []
|
|
119
|
+
|
|
120
|
+
if (failed.length) {
|
|
121
|
+
return {
|
|
122
|
+
revalidated,
|
|
123
|
+
warning:
|
|
124
|
+
'Some pages could not be refreshed and will refresh on their own: ' + failed.join(', '),
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return { revalidated }
|
|
129
|
+
} catch (err: unknown) {
|
|
130
|
+
return { revalidated: [], warning: (err as Error).message }
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export const pageRevalidate: NodeHandlerGenerator = {
|
|
135
|
+
nodeType: 'page-revalidate',
|
|
136
|
+
executionEnv: 'server',
|
|
137
|
+
generateHandler(): string {
|
|
138
|
+
return handlerToString(page_revalidate)
|
|
139
|
+
},
|
|
140
|
+
}
|
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import { NodeHandlerGenerator, handlerToString } from '../types'
|
|
2
|
+
import {
|
|
3
|
+
COMMERCE_TRACKING_HELPER_SOURCE,
|
|
4
|
+
assertHandlerHasNoModuleRefs,
|
|
5
|
+
} from '../analytics/commerce-tracking'
|
|
6
|
+
|
|
7
|
+
// AMBIENT, not imported. A cross-module call compiles to
|
|
8
|
+
// `(0, commerce_tracking_1.trackCommerceStep)(...)`, which survives
|
|
9
|
+
// `.toString()` and throws ReferenceError in the browser — the runtime has no
|
|
10
|
+
// module scope. Declaring the name emits nothing and leaves a bare identifier
|
|
11
|
+
// that the appended helper source defines. See `commerce-tracking.ts`.
|
|
12
|
+
declare function trackCommerceStep(step: { name: string; detail?: Record<string, unknown> }): void
|
|
2
13
|
|
|
3
14
|
async function cart_add_item(config: any) {
|
|
4
15
|
const productId = config.productId
|
|
@@ -79,6 +90,16 @@ async function cart_add_item(config: any) {
|
|
|
79
90
|
if (typeof window !== 'undefined') {
|
|
80
91
|
window.dispatchEvent(new CustomEvent('teleport:cart-changed'))
|
|
81
92
|
}
|
|
93
|
+
// Reported AFTER the write succeeds, so the funnel counts carts that were
|
|
94
|
+
// really changed rather than clicks that were merely attempted.
|
|
95
|
+
trackCommerceStep({
|
|
96
|
+
name: 'add_to_cart',
|
|
97
|
+
detail: {
|
|
98
|
+
currency,
|
|
99
|
+
value: price * quantity,
|
|
100
|
+
items: [{ item_id: productId, item_name: name, price, quantity }],
|
|
101
|
+
},
|
|
102
|
+
})
|
|
82
103
|
return {
|
|
83
104
|
id: cart[existingIndex].id,
|
|
84
105
|
productId,
|
|
@@ -110,6 +131,14 @@ async function cart_add_item(config: any) {
|
|
|
110
131
|
if (typeof window !== 'undefined') {
|
|
111
132
|
window.dispatchEvent(new CustomEvent('teleport:cart-changed'))
|
|
112
133
|
}
|
|
134
|
+
trackCommerceStep({
|
|
135
|
+
name: 'add_to_cart',
|
|
136
|
+
detail: {
|
|
137
|
+
currency,
|
|
138
|
+
value: price * initialQty,
|
|
139
|
+
items: [{ item_id: productId, item_name: name, price, quantity: initialQty }],
|
|
140
|
+
},
|
|
141
|
+
})
|
|
113
142
|
return { id: newItem.id, productId, quantity: initialQty }
|
|
114
143
|
}
|
|
115
144
|
} catch (err: unknown) {
|
|
@@ -120,6 +149,11 @@ export const cartAddItem: NodeHandlerGenerator = {
|
|
|
120
149
|
nodeType: 'cart-add-item',
|
|
121
150
|
executionEnv: 'client',
|
|
122
151
|
generateHandler(): string {
|
|
123
|
-
|
|
152
|
+
// The helper is concatenated rather than imported: the handler ships as a
|
|
153
|
+
// serialized function body with no module scope. See `commerce-tracking.ts`.
|
|
154
|
+
return assertHandlerHasNoModuleRefs(
|
|
155
|
+
handlerToString(cart_add_item) + '\n' + COMMERCE_TRACKING_HELPER_SOURCE,
|
|
156
|
+
'cart-add-item'
|
|
157
|
+
)
|
|
124
158
|
},
|
|
125
159
|
}
|
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import { NodeHandlerGenerator, handlerToString } from '../types'
|
|
2
|
+
import {
|
|
3
|
+
COMMERCE_TRACKING_HELPER_SOURCE,
|
|
4
|
+
assertHandlerHasNoModuleRefs,
|
|
5
|
+
} from '../analytics/commerce-tracking'
|
|
6
|
+
|
|
7
|
+
// AMBIENT, not imported. A cross-module call compiles to
|
|
8
|
+
// `(0, commerce_tracking_1.trackCommerceStep)(...)`, which survives
|
|
9
|
+
// `.toString()` and throws ReferenceError in the browser — the runtime has no
|
|
10
|
+
// module scope. Declaring the name emits nothing and leaves a bare identifier
|
|
11
|
+
// that the appended helper source defines. See `commerce-tracking.ts`.
|
|
12
|
+
declare function trackCommerceStep(step: { name: string; detail?: Record<string, unknown> }): void
|
|
2
13
|
|
|
3
14
|
async function cart_remove_item(config: any) {
|
|
4
15
|
const itemId = config.itemId
|
|
@@ -6,12 +17,33 @@ async function cart_remove_item(config: any) {
|
|
|
6
17
|
try {
|
|
7
18
|
const raw = localStorage.getItem('workflow_cart')
|
|
8
19
|
const cart: any[] = raw ? JSON.parse(raw) : []
|
|
20
|
+
const removed = cart.find((item: any) => item.id === itemId)
|
|
9
21
|
const newCart = cart.filter((item: any) => item.id !== itemId)
|
|
10
22
|
|
|
11
23
|
localStorage.setItem('workflow_cart', JSON.stringify(newCart))
|
|
12
24
|
if (typeof window !== 'undefined') {
|
|
13
25
|
window.dispatchEvent(new CustomEvent('teleport:cart-changed'))
|
|
14
26
|
}
|
|
27
|
+
// Only when a line was actually removed: a remove call for an id that is no
|
|
28
|
+
// longer in the cart (a double click, a stale button) changed nothing, and
|
|
29
|
+
// counting it would overstate the abandonment the merchant reads here.
|
|
30
|
+
if (removed) {
|
|
31
|
+
trackCommerceStep({
|
|
32
|
+
name: 'remove_from_cart',
|
|
33
|
+
detail: {
|
|
34
|
+
currency: removed.currency,
|
|
35
|
+
value: (Number(removed.price) || 0) * (Number(removed.quantity) || 0),
|
|
36
|
+
items: [
|
|
37
|
+
{
|
|
38
|
+
item_id: removed.productId,
|
|
39
|
+
item_name: removed.name,
|
|
40
|
+
price: removed.price,
|
|
41
|
+
quantity: removed.quantity,
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
})
|
|
46
|
+
}
|
|
15
47
|
return { success: true, cart: newCart }
|
|
16
48
|
} catch (err: unknown) {
|
|
17
49
|
return { success: false, error: (err as Error).message }
|
|
@@ -21,6 +53,9 @@ export const cartRemoveItem: NodeHandlerGenerator = {
|
|
|
21
53
|
nodeType: 'cart-remove-item',
|
|
22
54
|
executionEnv: 'client',
|
|
23
55
|
generateHandler(): string {
|
|
24
|
-
return
|
|
56
|
+
return assertHandlerHasNoModuleRefs(
|
|
57
|
+
handlerToString(cart_remove_item) + '\n' + COMMERCE_TRACKING_HELPER_SOURCE,
|
|
58
|
+
'cart-remove-item'
|
|
59
|
+
)
|
|
25
60
|
},
|
|
26
61
|
}
|
|
@@ -30,6 +30,49 @@ async function data_create_item(config: any, context: any) {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
// A value the client→server hand-off could not carry (see pruneContext: any
|
|
34
|
+
// context entry over its budget crosses as { __truncated: true }) must never
|
|
35
|
+
// be INSERTed — the placeholder object would be stored as the column's
|
|
36
|
+
// content. Dropping the mapping leaves the column at its default, which is
|
|
37
|
+
// recoverable; a row whose body reads {"__truncated":true} is not.
|
|
38
|
+
const __isUnwritableValue = function (__v: any) {
|
|
39
|
+
return (
|
|
40
|
+
!!__v &&
|
|
41
|
+
typeof __v === 'object' &&
|
|
42
|
+
(__v.__truncated === true || __v.__serializationError === true)
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
const __warnDroppedMapping = function (__col: any) {
|
|
46
|
+
if (typeof console !== 'undefined' && console.warn) {
|
|
47
|
+
console.warn(
|
|
48
|
+
'[workflow] data-create-item left "' +
|
|
49
|
+
(__col || 'unknown') +
|
|
50
|
+
'" unset: the value was too large to reach the server'
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
let effectiveColumnMappings: any = columnMappings
|
|
55
|
+
if (Array.isArray(columnMappings)) {
|
|
56
|
+
effectiveColumnMappings = columnMappings.filter(function (__m: any) {
|
|
57
|
+
if (!(__m && typeof __m === 'object') || !__isUnwritableValue(__m.value)) {
|
|
58
|
+
return true
|
|
59
|
+
}
|
|
60
|
+
__warnDroppedMapping(__m.column || __m.name || __m.field)
|
|
61
|
+
return false
|
|
62
|
+
})
|
|
63
|
+
} else if (columnMappings && typeof columnMappings === 'object') {
|
|
64
|
+
effectiveColumnMappings = {}
|
|
65
|
+
const __ck = Object.keys(columnMappings)
|
|
66
|
+
for (let __ci = 0; __ci < __ck.length; __ci++) {
|
|
67
|
+
const __value = (columnMappings as any)[__ck[__ci]]
|
|
68
|
+
if (__isUnwritableValue(__value)) {
|
|
69
|
+
__warnDroppedMapping(__ck[__ci])
|
|
70
|
+
} else {
|
|
71
|
+
;(effectiveColumnMappings as any)[__ck[__ci]] = __value
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
33
76
|
// Surface the workflow's anonymous-user UUID to the data-api so a
|
|
34
77
|
// guest-checkout INSERT can recover its `user_id` from the
|
|
35
78
|
// resolve-user output instead of being NULL'd by the UUID
|
|
@@ -59,7 +102,7 @@ async function data_create_item(config: any, context: any) {
|
|
|
59
102
|
}
|
|
60
103
|
|
|
61
104
|
try {
|
|
62
|
-
const reqBody: any = { tableName, columnMappings }
|
|
105
|
+
const reqBody: any = { tableName, columnMappings: effectiveColumnMappings }
|
|
63
106
|
if (__anonymousUserId) {
|
|
64
107
|
reqBody.__anonymousUserId = __anonymousUserId
|
|
65
108
|
}
|
|
@@ -67,17 +67,53 @@ async function data_update_item(config: any, context: any) {
|
|
|
67
67
|
// guests.full_name from an un-prefilled guest_name input. OMIT such mappings so
|
|
68
68
|
// the column keeps its stored value. (An intentional null — e.g. a route-param
|
|
69
69
|
// sentinel degraded above — is preserved: only `undefined` is dropped.)
|
|
70
|
+
//
|
|
71
|
+
// A value the client→server hand-off could not carry (see pruneContext: any
|
|
72
|
+
// context entry over its budget crosses as { __truncated: true }) is dropped
|
|
73
|
+
// for the same reason and with the same result — the column KEEPS what it
|
|
74
|
+
// holds. Writing the placeholder instead destroys the stored value, which is
|
|
75
|
+
// how a rich-text field with an inlined image emptied a whole page of blocks.
|
|
76
|
+
const __isUnwritableValue = function (__v: any) {
|
|
77
|
+
if (__v === undefined) {
|
|
78
|
+
return true
|
|
79
|
+
}
|
|
80
|
+
if (!__v || typeof __v !== 'object') {
|
|
81
|
+
return false
|
|
82
|
+
}
|
|
83
|
+
return __v.__truncated === true || __v.__serializationError === true
|
|
84
|
+
}
|
|
85
|
+
const __warnDroppedMapping = function (__col: any) {
|
|
86
|
+
if (typeof console !== 'undefined' && console.warn) {
|
|
87
|
+
console.warn(
|
|
88
|
+
'[workflow] data-update-item kept the stored value of "' +
|
|
89
|
+
(__col || 'unknown') +
|
|
90
|
+
'": the new value was too large to reach the server (it stays as it was)'
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
70
94
|
let effectiveColumnMappings: any = columnMappings
|
|
71
95
|
if (Array.isArray(columnMappings)) {
|
|
72
96
|
effectiveColumnMappings = columnMappings.filter(function (__m: any) {
|
|
73
|
-
|
|
97
|
+
if (!(__m && typeof __m === 'object')) {
|
|
98
|
+
return true
|
|
99
|
+
}
|
|
100
|
+
if (!__isUnwritableValue(__m.value)) {
|
|
101
|
+
return true
|
|
102
|
+
}
|
|
103
|
+
if (__m.value !== undefined) {
|
|
104
|
+
__warnDroppedMapping(__m.column || __m.name || __m.field)
|
|
105
|
+
}
|
|
106
|
+
return false
|
|
74
107
|
})
|
|
75
108
|
} else if (columnMappings && typeof columnMappings === 'object') {
|
|
76
109
|
effectiveColumnMappings = {}
|
|
77
110
|
const __ck = Object.keys(columnMappings)
|
|
78
111
|
for (let __ci = 0; __ci < __ck.length; __ci++) {
|
|
79
|
-
|
|
80
|
-
|
|
112
|
+
const __value = (columnMappings as any)[__ck[__ci]]
|
|
113
|
+
if (!__isUnwritableValue(__value)) {
|
|
114
|
+
;(effectiveColumnMappings as any)[__ck[__ci]] = __value
|
|
115
|
+
} else if (__value !== undefined) {
|
|
116
|
+
__warnDroppedMapping(__ck[__ci])
|
|
81
117
|
}
|
|
82
118
|
}
|
|
83
119
|
}
|
package/src/nodes/index.ts
CHANGED
|
@@ -39,6 +39,7 @@ import { browserWriteClipboard } from './browser/browser-write-clipboard'
|
|
|
39
39
|
import { cacheGet } from './cache/cache-get'
|
|
40
40
|
import { cacheSet } from './cache/cache-set'
|
|
41
41
|
import { cacheInvalidate } from './cache/cache-invalidate'
|
|
42
|
+
import { pageRevalidate } from './cache/page-revalidate'
|
|
42
43
|
import { cartAddItem } from './cart/cart-add-item'
|
|
43
44
|
import { cartClear } from './cart/cart-clear'
|
|
44
45
|
import { cartGetItems } from './cart/cart-get-items'
|
|
@@ -97,6 +98,7 @@ import { navigationRefreshPage } from './navigation/navigation-refresh-page'
|
|
|
97
98
|
import { paymentCancelPlan } from './payment/payment-cancel-plan'
|
|
98
99
|
import { paymentChargeUser } from './payment/payment-charge-user'
|
|
99
100
|
import { paymentCreateCustomer } from './payment/payment-create-customer'
|
|
101
|
+
import { paymentRefund } from './payment/payment-refund'
|
|
100
102
|
import { paymentCreateProduct } from './payment/payment-create-product'
|
|
101
103
|
import { paymentCreateSubscription } from './payment/payment-create-subscription'
|
|
102
104
|
import { paymentGetCustomer } from './payment/payment-get-customer'
|
|
@@ -262,6 +264,7 @@ export const nodeRegistry: Record<string, NodeHandlerGenerator> = {
|
|
|
262
264
|
'navigation-refresh-page': navigationRefreshPage,
|
|
263
265
|
'payment-cancel-plan': paymentCancelPlan,
|
|
264
266
|
'payment-charge-user': paymentChargeUser,
|
|
267
|
+
'payment-refund': paymentRefund,
|
|
265
268
|
'payment-create-customer': paymentCreateCustomer,
|
|
266
269
|
'payment-create-product': paymentCreateProduct,
|
|
267
270
|
'payment-create-subscription': paymentCreateSubscription,
|
|
@@ -297,6 +300,7 @@ export const nodeRegistry: Record<string, NodeHandlerGenerator> = {
|
|
|
297
300
|
'cache-get': cacheGet,
|
|
298
301
|
'cache-set': cacheSet,
|
|
299
302
|
'cache-invalidate': cacheInvalidate,
|
|
303
|
+
'page-revalidate': pageRevalidate,
|
|
300
304
|
'toast-show': toastShow,
|
|
301
305
|
'transform-array': transformArray,
|
|
302
306
|
'transform-calculate': transformCalculate,
|