@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.
- package/__tests__/graph-utils-topological-order.test.ts +108 -0
- package/__tests__/runtime-template-token-resolution.test.ts +12 -9
- package/dist/cjs/graph-utils.d.ts +7 -0
- package/dist/cjs/graph-utils.d.ts.map +1 -1
- package/dist/cjs/graph-utils.js +52 -1
- package/dist/cjs/graph-utils.js.map +1 -1
- package/dist/cjs/nodes/data/data-count.d.ts.map +1 -1
- package/dist/cjs/nodes/data/data-count.js +8 -7
- package/dist/cjs/nodes/data/data-count.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 +9 -7
- package/dist/cjs/nodes/data/data-delete-item.js.map +1 -1
- package/dist/cjs/nodes/data/data-select.d.ts.map +1 -1
- package/dist/cjs/nodes/data/data-select.js +12 -8
- 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 +11 -9
- package/dist/cjs/nodes/data/data-update-item.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/graph-utils.d.ts +7 -0
- package/dist/esm/graph-utils.d.ts.map +1 -1
- package/dist/esm/graph-utils.js +50 -0
- package/dist/esm/graph-utils.js.map +1 -1
- package/dist/esm/nodes/data/data-count.d.ts.map +1 -1
- package/dist/esm/nodes/data/data-count.js +8 -7
- package/dist/esm/nodes/data/data-count.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 +9 -7
- package/dist/esm/nodes/data/data-delete-item.js.map +1 -1
- package/dist/esm/nodes/data/data-select.d.ts.map +1 -1
- package/dist/esm/nodes/data/data-select.js +12 -8
- 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 +11 -9
- package/dist/esm/nodes/data/data-update-item.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/graph-utils.ts +52 -0
- package/src/nodes/data/data-count.ts +10 -8
- package/src/nodes/data/data-delete-item.ts +11 -8
- package/src/nodes/data/data-select.ts +14 -9
- package/src/nodes/data/data-update-item.ts +13 -10
package/src/graph-utils.ts
CHANGED
|
@@ -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
|
|
10
|
-
//
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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)
|
|
16
|
-
//
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
|
13
|
-
//
|
|
14
|
-
//
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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)) {
|