@teleporthq/teleport-plugin-next-workflows 0.43.30 → 0.43.32

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 (42) hide show
  1. package/__tests__/graph-utils-topological-order.test.ts +108 -0
  2. package/__tests__/runtime-template-token-resolution.test.ts +12 -9
  3. package/dist/cjs/graph-utils.d.ts +7 -0
  4. package/dist/cjs/graph-utils.d.ts.map +1 -1
  5. package/dist/cjs/graph-utils.js +52 -1
  6. package/dist/cjs/graph-utils.js.map +1 -1
  7. package/dist/cjs/nodes/data/data-count.d.ts.map +1 -1
  8. package/dist/cjs/nodes/data/data-count.js +8 -7
  9. package/dist/cjs/nodes/data/data-count.js.map +1 -1
  10. package/dist/cjs/nodes/data/data-delete-item.d.ts.map +1 -1
  11. package/dist/cjs/nodes/data/data-delete-item.js +9 -7
  12. package/dist/cjs/nodes/data/data-delete-item.js.map +1 -1
  13. package/dist/cjs/nodes/data/data-select.d.ts.map +1 -1
  14. package/dist/cjs/nodes/data/data-select.js +12 -8
  15. package/dist/cjs/nodes/data/data-select.js.map +1 -1
  16. package/dist/cjs/nodes/data/data-update-item.d.ts.map +1 -1
  17. package/dist/cjs/nodes/data/data-update-item.js +11 -9
  18. package/dist/cjs/nodes/data/data-update-item.js.map +1 -1
  19. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  20. package/dist/esm/graph-utils.d.ts +7 -0
  21. package/dist/esm/graph-utils.d.ts.map +1 -1
  22. package/dist/esm/graph-utils.js +50 -0
  23. package/dist/esm/graph-utils.js.map +1 -1
  24. package/dist/esm/nodes/data/data-count.d.ts.map +1 -1
  25. package/dist/esm/nodes/data/data-count.js +8 -7
  26. package/dist/esm/nodes/data/data-count.js.map +1 -1
  27. package/dist/esm/nodes/data/data-delete-item.d.ts.map +1 -1
  28. package/dist/esm/nodes/data/data-delete-item.js +9 -7
  29. package/dist/esm/nodes/data/data-delete-item.js.map +1 -1
  30. package/dist/esm/nodes/data/data-select.d.ts.map +1 -1
  31. package/dist/esm/nodes/data/data-select.js +12 -8
  32. package/dist/esm/nodes/data/data-select.js.map +1 -1
  33. package/dist/esm/nodes/data/data-update-item.d.ts.map +1 -1
  34. package/dist/esm/nodes/data/data-update-item.js +11 -9
  35. package/dist/esm/nodes/data/data-update-item.js.map +1 -1
  36. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  37. package/package.json +2 -2
  38. package/src/graph-utils.ts +52 -0
  39. package/src/nodes/data/data-count.ts +10 -8
  40. package/src/nodes/data/data-delete-item.ts +11 -8
  41. package/src/nodes/data/data-select.ts +14 -9
  42. package/src/nodes/data/data-update-item.ts +13 -10
@@ -23,6 +23,27 @@ export const getEdgesTo = (nodeId: string, edges: UIDLWorkflowEdge[]): UIDLWorkf
23
23
  return edges.filter((e) => e.target === nodeId)
24
24
  }
25
25
 
26
+ /**
27
+ * Recursively collect every workflow-context node id a config references, i.e.
28
+ * the ids inside `{ type: 'workflowContext', nodeId: '<id>', path: [...] }`
29
+ * objects embedded anywhere in a node's config (filter values, column mappings,
30
+ * loop collections, custom-js params, …).
31
+ */
32
+ export const collectWorkflowContextNodeIds = (value: unknown, out: Set<string>): void => {
33
+ if (!value || typeof value !== 'object') {
34
+ return
35
+ }
36
+ if (Array.isArray(value)) {
37
+ value.forEach((item) => collectWorkflowContextNodeIds(item, out))
38
+ return
39
+ }
40
+ const record = value as Record<string, unknown>
41
+ if (record.type === 'workflowContext' && typeof record.nodeId === 'string') {
42
+ out.add(record.nodeId)
43
+ }
44
+ Object.values(record).forEach((child) => collectWorkflowContextNodeIds(child, out))
45
+ }
46
+
26
47
  export const getTopologicalOrder = (
27
48
  nodes: UIDLWorkflowNode[],
28
49
  edges: UIDLWorkflowEdge[]
@@ -44,6 +65,37 @@ export const getTopologicalOrder = (
44
65
  }
45
66
  })
46
67
 
68
+ // Virtual dependency edges from IMPLICIT data dependencies: a node that
69
+ // consumes another node's output via a `workflowContext` reference in its
70
+ // config MUST run after that node, even when no explicit edge wires them.
71
+ // Without this, a `data-create-item` that reads a `state-get-local-state`
72
+ // result only through its columnMappings is treated as in-degree 0 and can be
73
+ // ordered (and segmented) BEFORE the state read — so on the server it writes
74
+ // null for that column (run 0a5f554e: `titles.type_field` NOT-NULL violation;
75
+ // the "Add Asset" draft rows came through as "N/A" for the same reason).
76
+ // Control-flow nodes (loop/if/switch/parallel) are skipped — their outputs
77
+ // (e.g. a loop's `currentItem`) are scoped by dedicated edge handles, not a
78
+ // linear data dependency. Edges are added to this LOCAL graph only (never to
79
+ // `workflow.edges`), so loop/branch extraction elsewhere is unaffected.
80
+ const nodeTypeById = new Map(nodes.map((n) => [n.id, n.type]))
81
+ nodes.forEach((node) => {
82
+ const referencedIds = new Set<string>()
83
+ collectWorkflowContextNodeIds(node.config, referencedIds)
84
+ referencedIds.forEach((refId) => {
85
+ if (refId === node.id || !nodeIds.has(refId)) {
86
+ return
87
+ }
88
+ if (isControlFlowNode(nodeTypeById.get(refId) || '')) {
89
+ return
90
+ }
91
+ if (adjacency[refId].includes(node.id)) {
92
+ return
93
+ }
94
+ adjacency[refId].push(node.id)
95
+ inDegree[node.id]++
96
+ })
97
+ })
98
+
47
99
  const queue: string[] = []
48
100
  nodeIdArr.forEach((id) => {
49
101
  if (inDegree[id] === 0) {
@@ -6,8 +6,9 @@ async function data_count(config: any, context: any) {
6
6
  const filters = config.filters || []
7
7
  const baseUrl = (context && context.__baseUrl) || ''
8
8
 
9
- // Unresolved route-param sentinel in a filter → validation error (see
10
- // resolveTemplateTokenString in runtime-utils) instead of counting nothing.
9
+ // Unresolved route-param sentinel in a filter (see resolveTemplateTokenString
10
+ // in runtime-utils). DEGRADE: return count:0 WITHOUT an `error` so the executor
11
+ // does not throw and abort the whole workflow.
11
12
  for (let __fi = 0; __fi < filters.length; __fi++) {
12
13
  const __f: any = filters[__fi]
13
14
  if (
@@ -16,13 +17,14 @@ async function data_count(config: any, context: any) {
16
17
  __f.destination === '__TQ_UNRESOLVED_ROUTE_PARAM__')
17
18
  ) {
18
19
  const __col = __f.column || __f.source || __f.field || 'unknown'
19
- return {
20
- count: 0,
21
- error:
22
- 'Filter on "' +
23
- __col +
24
- '" requires the page route parameter, which is not available in this context',
20
+ if (typeof console !== 'undefined' && console.warn) {
21
+ console.warn(
22
+ '[workflow] data-count skipped — filter on "' +
23
+ __col +
24
+ '" needs a page route param not available here; count 0 (workflow continues)'
25
+ )
25
26
  }
27
+ return { count: 0, __skippedUnavailableFilter: true }
26
28
  }
27
29
  }
28
30
 
@@ -17,16 +17,19 @@ async function data_delete_item(config: any, context: any) {
17
17
  (__f.value === '__TQ_UNRESOLVED_ROUTE_PARAM__' ||
18
18
  __f.destination === '__TQ_UNRESOLVED_ROUTE_PARAM__')
19
19
  ) {
20
+ // DEGRADE to a NO-OP: return deletedCount:0 WITHOUT `error` and WITHOUT
21
+ // success:false so the executor does not throw and abort the whole
22
+ // workflow. A DELETE must NEVER drop the filter and run unscoped (that
23
+ // would delete every row) — no-op is the only safe degrade.
20
24
  const __col = __f.column || __f.source || __f.field || 'unknown'
21
- return {
22
- deletedId: null,
23
- success: false,
24
- deletedCount: 0,
25
- error:
26
- 'Filter on "' +
27
- __col +
28
- '" requires the page route parameter, which is not available in this context',
25
+ if (typeof console !== 'undefined' && console.warn) {
26
+ console.warn(
27
+ '[workflow] data-delete-item skipped (no-op) — filter on "' +
28
+ __col +
29
+ '" needs a page route param not available here; nothing deleted (workflow continues)'
30
+ )
29
31
  }
32
+ return { deletedId: null, success: true, deletedCount: 0, __skippedUnavailableFilter: true }
30
33
  }
31
34
  }
32
35
 
@@ -12,8 +12,13 @@ async function data_select(config: any, context: any) {
12
12
  const baseUrl = (context && context.__baseUrl) || ''
13
13
 
14
14
  // A filter whose value is the unresolved route-param sentinel (see
15
- // resolveTemplateTokenString in runtime-utils) must fail loudly instead of
16
- // querying 'WHERE col = <sentinel>' and silently returning 0 rows.
15
+ // resolveTemplateTokenString in runtime-utils) e.g. a select scoped to
16
+ // {{Current Page Entity.id}} that runs on a page with no such route param
17
+ // (a create page, or a mis-generated filter). DEGRADE GRACEFULLY: return an
18
+ // empty result WITHOUT an `error` so the executor does not throw and abort the
19
+ // WHOLE workflow (a single unresolvable filter must never kill the note-create
20
+ // submit). Never fall through to an unscoped query. Observable via the warn +
21
+ // the __skippedUnavailableFilter marker.
17
22
  for (let __fi = 0; __fi < filters.length; __fi++) {
18
23
  const __f: any = filters[__fi]
19
24
  if (
@@ -22,14 +27,14 @@ async function data_select(config: any, context: any) {
22
27
  __f.destination === '__TQ_UNRESOLVED_ROUTE_PARAM__')
23
28
  ) {
24
29
  const __col = __f.column || __f.source || __f.field || 'unknown'
25
- return {
26
- rows: [],
27
- count: 0,
28
- error:
29
- 'Filter on "' +
30
- __col +
31
- '" requires the page route parameter, which is not available in this context',
30
+ if (typeof console !== 'undefined' && console.warn) {
31
+ console.warn(
32
+ '[workflow] data-select skipped — filter on "' +
33
+ __col +
34
+ '" needs a page route param not available here; returning empty rows (workflow continues)'
35
+ )
32
36
  }
37
+ return { rows: [], count: 0, __skippedUnavailableFilter: true }
33
38
  }
34
39
  }
35
40
 
@@ -9,9 +9,12 @@ async function data_update_item(config: any, context: any) {
9
9
  const __env = (globalThis as any).process && (globalThis as any).process.env
10
10
 
11
11
  // Unresolved route-param sentinel (see resolveTemplateTokenString in
12
- // runtime-utils): in a filter it is a validation error an UPDATE keyed on
13
- // it would match 0 rows and report failure with no hint why. In a
14
- // columnMapping it degrades to null so the write itself survives.
12
+ // runtime-utils): in a filter, an UPDATE keyed on it cannot identify its row.
13
+ // DEGRADE to a NO-OP return updatedCount:0 WITHOUT an `error` so the executor
14
+ // does not throw and abort the whole workflow. CRITICAL: never drop the filter
15
+ // and run an UNSCOPED update (that would rewrite every row); no-op is the only
16
+ // safe degrade. (A columnMapping sentinel still degrades to null below so the
17
+ // write's VALUES survive.) Observable via the warn + __skippedUnavailableFilter.
15
18
  for (let __fi = 0; __fi < filters.length; __fi++) {
16
19
  const __f: any = filters[__fi]
17
20
  if (
@@ -20,14 +23,14 @@ async function data_update_item(config: any, context: any) {
20
23
  __f.destination === '__TQ_UNRESOLVED_ROUTE_PARAM__')
21
24
  ) {
22
25
  const __col = __f.column || __f.source || __f.field || 'unknown'
23
- return {
24
- id: null,
25
- updatedCount: 0,
26
- error:
27
- 'Filter on "' +
28
- __col +
29
- '" requires the page route parameter, which is not available in this context',
26
+ if (typeof console !== 'undefined' && console.warn) {
27
+ console.warn(
28
+ '[workflow] data-update-item skipped (no-op) — filter on "' +
29
+ __col +
30
+ '" needs a page route param not available here; no rows updated (workflow continues)'
31
+ )
30
32
  }
33
+ return { id: null, updatedCount: 0, success: true, __skippedUnavailableFilter: true }
31
34
  }
32
35
  }
33
36
  if (Array.isArray(columnMappings)) {