@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.
Files changed (75) hide show
  1. package/__tests__/data-update-item-runtime.test.ts +39 -0
  2. package/__tests__/page-revalidate-node.test.ts +276 -0
  3. package/__tests__/payment-refund.test.ts +135 -0
  4. package/dist/cjs/nodes/analytics/commerce-tracking.d.ts +46 -0
  5. package/dist/cjs/nodes/analytics/commerce-tracking.d.ts.map +1 -0
  6. package/dist/cjs/nodes/analytics/commerce-tracking.js +94 -0
  7. package/dist/cjs/nodes/analytics/commerce-tracking.js.map +1 -0
  8. package/dist/cjs/nodes/cache/page-revalidate.d.ts +3 -0
  9. package/dist/cjs/nodes/cache/page-revalidate.d.ts.map +1 -0
  10. package/dist/cjs/nodes/cache/page-revalidate.js +125 -0
  11. package/dist/cjs/nodes/cache/page-revalidate.js.map +1 -0
  12. package/dist/cjs/nodes/cart/cart-add-item.d.ts.map +1 -1
  13. package/dist/cjs/nodes/cart/cart-add-item.js +22 -1
  14. package/dist/cjs/nodes/cart/cart-add-item.js.map +1 -1
  15. package/dist/cjs/nodes/cart/cart-remove-item.d.ts.map +1 -1
  16. package/dist/cjs/nodes/cart/cart-remove-item.js +23 -1
  17. package/dist/cjs/nodes/cart/cart-remove-item.js.map +1 -1
  18. package/dist/cjs/nodes/data/data-create-item.d.ts.map +1 -1
  19. package/dist/cjs/nodes/data/data-create-item.js +42 -1
  20. package/dist/cjs/nodes/data/data-create-item.js.map +1 -1
  21. package/dist/cjs/nodes/data/data-update-item.d.ts.map +1 -1
  22. package/dist/cjs/nodes/data/data-update-item.js +38 -3
  23. package/dist/cjs/nodes/data/data-update-item.js.map +1 -1
  24. package/dist/cjs/nodes/index.d.ts.map +1 -1
  25. package/dist/cjs/nodes/index.js +3 -1
  26. package/dist/cjs/nodes/index.js.map +1 -1
  27. package/dist/cjs/nodes/payment/payment-refund.d.ts +3 -0
  28. package/dist/cjs/nodes/payment/payment-refund.d.ts.map +1 -0
  29. package/dist/cjs/nodes/payment/payment-refund.js +446 -0
  30. package/dist/cjs/nodes/payment/payment-refund.js.map +1 -0
  31. package/dist/cjs/secret-collector.d.ts.map +1 -1
  32. package/dist/cjs/secret-collector.js +3 -0
  33. package/dist/cjs/secret-collector.js.map +1 -1
  34. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  35. package/dist/esm/nodes/analytics/commerce-tracking.d.ts +46 -0
  36. package/dist/esm/nodes/analytics/commerce-tracking.d.ts.map +1 -0
  37. package/dist/esm/nodes/analytics/commerce-tracking.js +90 -0
  38. package/dist/esm/nodes/analytics/commerce-tracking.js.map +1 -0
  39. package/dist/esm/nodes/cache/page-revalidate.d.ts +3 -0
  40. package/dist/esm/nodes/cache/page-revalidate.d.ts.map +1 -0
  41. package/dist/esm/nodes/cache/page-revalidate.js +122 -0
  42. package/dist/esm/nodes/cache/page-revalidate.js.map +1 -0
  43. package/dist/esm/nodes/cart/cart-add-item.d.ts.map +1 -1
  44. package/dist/esm/nodes/cart/cart-add-item.js +22 -1
  45. package/dist/esm/nodes/cart/cart-add-item.js.map +1 -1
  46. package/dist/esm/nodes/cart/cart-remove-item.d.ts.map +1 -1
  47. package/dist/esm/nodes/cart/cart-remove-item.js +23 -1
  48. package/dist/esm/nodes/cart/cart-remove-item.js.map +1 -1
  49. package/dist/esm/nodes/data/data-create-item.d.ts.map +1 -1
  50. package/dist/esm/nodes/data/data-create-item.js +42 -1
  51. package/dist/esm/nodes/data/data-create-item.js.map +1 -1
  52. package/dist/esm/nodes/data/data-update-item.d.ts.map +1 -1
  53. package/dist/esm/nodes/data/data-update-item.js +38 -3
  54. package/dist/esm/nodes/data/data-update-item.js.map +1 -1
  55. package/dist/esm/nodes/index.d.ts.map +1 -1
  56. package/dist/esm/nodes/index.js +3 -1
  57. package/dist/esm/nodes/index.js.map +1 -1
  58. package/dist/esm/nodes/payment/payment-refund.d.ts +3 -0
  59. package/dist/esm/nodes/payment/payment-refund.d.ts.map +1 -0
  60. package/dist/esm/nodes/payment/payment-refund.js +443 -0
  61. package/dist/esm/nodes/payment/payment-refund.js.map +1 -0
  62. package/dist/esm/secret-collector.d.ts.map +1 -1
  63. package/dist/esm/secret-collector.js +3 -0
  64. package/dist/esm/secret-collector.js.map +1 -1
  65. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  66. package/package.json +4 -4
  67. package/src/nodes/analytics/commerce-tracking.ts +93 -0
  68. package/src/nodes/cache/page-revalidate.ts +140 -0
  69. package/src/nodes/cart/cart-add-item.ts +35 -1
  70. package/src/nodes/cart/cart-remove-item.ts +36 -1
  71. package/src/nodes/data/data-create-item.ts +44 -1
  72. package/src/nodes/data/data-update-item.ts +39 -3
  73. package/src/nodes/index.ts +4 -0
  74. package/src/nodes/payment/payment-refund.ts +512 -0
  75. 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.62",
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.62",
70
- "@teleporthq/teleport-types": "^0.43.62"
69
+ "@teleporthq/teleport-shared": "^0.43.63",
70
+ "@teleporthq/teleport-types": "^0.43.63"
71
71
  },
72
- "gitHead": "7f737c4c1bbabda1d95a0bee8a7d7c488ba17990"
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
- return handlerToString(cart_add_item)
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 handlerToString(cart_remove_item)
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
- return !(__m && typeof __m === 'object' && __m.value === undefined)
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
- if ((columnMappings as any)[__ck[__ci]] !== undefined) {
80
- ;(effectiveColumnMappings as any)[__ck[__ci]] = (columnMappings as any)[__ck[__ci]]
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
  }
@@ -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,