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

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 (57) hide show
  1. package/__tests__/_helpers/load-handler.ts +11 -1
  2. package/__tests__/file-storage-upload-picked-files.test.ts +135 -0
  3. package/__tests__/runtime-operator-aliases.test.ts +180 -0
  4. package/__tests__/runtime-template-token-resolution.test.ts +225 -0
  5. package/__tests__/state-update-array-type-guard.test.ts +172 -0
  6. package/dist/cjs/executor-generator.d.ts.map +1 -1
  7. package/dist/cjs/executor-generator.js +134 -5
  8. package/dist/cjs/executor-generator.js.map +1 -1
  9. package/dist/cjs/nodes/data/data-count.d.ts.map +1 -1
  10. package/dist/cjs/nodes/data/data-count.js +16 -0
  11. package/dist/cjs/nodes/data/data-count.js.map +1 -1
  12. package/dist/cjs/nodes/data/data-create-item.d.ts.map +1 -1
  13. package/dist/cjs/nodes/data/data-create-item.js +20 -0
  14. package/dist/cjs/nodes/data/data-create-item.js.map +1 -1
  15. package/dist/cjs/nodes/data/data-delete-item.d.ts.map +1 -1
  16. package/dist/cjs/nodes/data/data-delete-item.js +19 -0
  17. package/dist/cjs/nodes/data/data-delete-item.js.map +1 -1
  18. package/dist/cjs/nodes/data/data-select.d.ts.map +1 -1
  19. package/dist/cjs/nodes/data/data-select.js +18 -0
  20. package/dist/cjs/nodes/data/data-select.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 +36 -0
  23. package/dist/cjs/nodes/data/data-update-item.js.map +1 -1
  24. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  25. package/dist/cjs/workflow-component-plugin.d.ts.map +1 -1
  26. package/dist/cjs/workflow-component-plugin.js +56 -9
  27. package/dist/cjs/workflow-component-plugin.js.map +1 -1
  28. package/dist/esm/executor-generator.d.ts.map +1 -1
  29. package/dist/esm/executor-generator.js +134 -5
  30. package/dist/esm/executor-generator.js.map +1 -1
  31. package/dist/esm/nodes/data/data-count.d.ts.map +1 -1
  32. package/dist/esm/nodes/data/data-count.js +16 -0
  33. package/dist/esm/nodes/data/data-count.js.map +1 -1
  34. package/dist/esm/nodes/data/data-create-item.d.ts.map +1 -1
  35. package/dist/esm/nodes/data/data-create-item.js +20 -0
  36. package/dist/esm/nodes/data/data-create-item.js.map +1 -1
  37. package/dist/esm/nodes/data/data-delete-item.d.ts.map +1 -1
  38. package/dist/esm/nodes/data/data-delete-item.js +19 -0
  39. package/dist/esm/nodes/data/data-delete-item.js.map +1 -1
  40. package/dist/esm/nodes/data/data-select.d.ts.map +1 -1
  41. package/dist/esm/nodes/data/data-select.js +18 -0
  42. package/dist/esm/nodes/data/data-select.js.map +1 -1
  43. package/dist/esm/nodes/data/data-update-item.d.ts.map +1 -1
  44. package/dist/esm/nodes/data/data-update-item.js +36 -0
  45. package/dist/esm/nodes/data/data-update-item.js.map +1 -1
  46. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  47. package/dist/esm/workflow-component-plugin.d.ts.map +1 -1
  48. package/dist/esm/workflow-component-plugin.js +56 -9
  49. package/dist/esm/workflow-component-plugin.js.map +1 -1
  50. package/package.json +2 -2
  51. package/src/executor-generator.ts +134 -5
  52. package/src/nodes/data/data-count.ts +20 -0
  53. package/src/nodes/data/data-create-item.ts +19 -0
  54. package/src/nodes/data/data-delete-item.ts +23 -0
  55. package/src/nodes/data/data-select.ts +22 -0
  56. package/src/nodes/data/data-update-item.ts +38 -0
  57. package/src/workflow-component-plugin.ts +61 -3
@@ -6,6 +6,26 @@ 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.
11
+ for (let __fi = 0; __fi < filters.length; __fi++) {
12
+ const __f: any = filters[__fi]
13
+ if (
14
+ __f &&
15
+ (__f.value === '__TQ_UNRESOLVED_ROUTE_PARAM__' ||
16
+ __f.destination === '__TQ_UNRESOLVED_ROUTE_PARAM__')
17
+ ) {
18
+ 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',
25
+ }
26
+ }
27
+ }
28
+
9
29
  try {
10
30
  const response = await fetch(baseUrl + '/api/data/' + dataSourceId + '/count', {
11
31
  method: 'POST',
@@ -6,6 +6,25 @@ async function data_create_item(config: any, context: any) {
6
6
  const columnMappings = config.columnMappings || {}
7
7
  const baseUrl = (context && context.__baseUrl) || ''
8
8
 
9
+ // An unresolved route-param sentinel (see resolveTemplateTokenString in
10
+ // runtime-utils) in a columnMapping degrades to null so the INSERT itself
11
+ // survives — only attribution-style columns lose their value.
12
+ if (Array.isArray(columnMappings)) {
13
+ for (let __mi = 0; __mi < columnMappings.length; __mi++) {
14
+ const __m: any = columnMappings[__mi]
15
+ if (__m && __m.value === '__TQ_UNRESOLVED_ROUTE_PARAM__') {
16
+ __m.value = null
17
+ }
18
+ }
19
+ } else if (columnMappings && typeof columnMappings === 'object') {
20
+ const __mKeys = Object.keys(columnMappings)
21
+ for (let __mi = 0; __mi < __mKeys.length; __mi++) {
22
+ if ((columnMappings as any)[__mKeys[__mi]] === '__TQ_UNRESOLVED_ROUTE_PARAM__') {
23
+ ;(columnMappings as any)[__mKeys[__mi]] = null
24
+ }
25
+ }
26
+ }
27
+
9
28
  // Surface the workflow's anonymous-user UUID to the data-api so a
10
29
  // guest-checkout INSERT can recover its `user_id` from the
11
30
  // resolve-user output instead of being NULL'd by the UUID
@@ -7,6 +7,29 @@ async function data_delete_item(config: any, context: any) {
7
7
  const baseUrl = (context && context.__baseUrl) || ''
8
8
  const __env = (globalThis as any).process && (globalThis as any).process.env
9
9
 
10
+ // Unresolved route-param sentinel in a filter → validation error (see
11
+ // resolveTemplateTokenString in runtime-utils). A DELETE must never run
12
+ // with a filter that silently matches nothing (or worse, gets dropped).
13
+ for (let __fi = 0; __fi < filters.length; __fi++) {
14
+ const __f: any = filters[__fi]
15
+ if (
16
+ __f &&
17
+ (__f.value === '__TQ_UNRESOLVED_ROUTE_PARAM__' ||
18
+ __f.destination === '__TQ_UNRESOLVED_ROUTE_PARAM__')
19
+ ) {
20
+ 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',
29
+ }
30
+ }
31
+ }
32
+
10
33
  try {
11
34
  const response = await fetch(baseUrl + '/api/data/' + dataSourceId + '/delete', {
12
35
  method: 'POST',
@@ -11,6 +11,28 @@ async function data_select(config: any, context: any) {
11
11
  const rawQueryUserPart = config.rawQueryUserPart
12
12
  const baseUrl = (context && context.__baseUrl) || ''
13
13
 
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.
17
+ for (let __fi = 0; __fi < filters.length; __fi++) {
18
+ const __f: any = filters[__fi]
19
+ if (
20
+ __f &&
21
+ (__f.value === '__TQ_UNRESOLVED_ROUTE_PARAM__' ||
22
+ __f.destination === '__TQ_UNRESOLVED_ROUTE_PARAM__')
23
+ ) {
24
+ 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',
32
+ }
33
+ }
34
+ }
35
+
14
36
  try {
15
37
  const payload: any = { tableName, filters, sorts, selectedColumns }
16
38
  if (limit !== undefined && limit !== null) {
@@ -8,6 +8,44 @@ async function data_update_item(config: any, context: any) {
8
8
  const baseUrl = (context && context.__baseUrl) || ''
9
9
  const __env = (globalThis as any).process && (globalThis as any).process.env
10
10
 
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.
15
+ for (let __fi = 0; __fi < filters.length; __fi++) {
16
+ const __f: any = filters[__fi]
17
+ if (
18
+ __f &&
19
+ (__f.value === '__TQ_UNRESOLVED_ROUTE_PARAM__' ||
20
+ __f.destination === '__TQ_UNRESOLVED_ROUTE_PARAM__')
21
+ ) {
22
+ 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',
30
+ }
31
+ }
32
+ }
33
+ if (Array.isArray(columnMappings)) {
34
+ for (let __mi = 0; __mi < columnMappings.length; __mi++) {
35
+ const __m: any = columnMappings[__mi]
36
+ if (__m && __m.value === '__TQ_UNRESOLVED_ROUTE_PARAM__') {
37
+ __m.value = null
38
+ }
39
+ }
40
+ } else if (columnMappings && typeof columnMappings === 'object') {
41
+ const __mKeys = Object.keys(columnMappings)
42
+ for (let __mi = 0; __mi < __mKeys.length; __mi++) {
43
+ if ((columnMappings as any)[__mKeys[__mi]] === '__TQ_UNRESOLVED_ROUTE_PARAM__') {
44
+ ;(columnMappings as any)[__mKeys[__mi]] = null
45
+ }
46
+ }
47
+ }
48
+
11
49
  // Same anonymous-user hint as data-create-item — a workflow that
12
50
  // updates a row written by a guest must be able to re-stamp its
13
51
  // ownership column with the same anon UUID rather than NULL.
@@ -327,7 +327,14 @@ export const createNextWorkflowPlugin: ComponentPluginFactory<WorkflowPluginConf
327
327
  const hasCurrentUserGlobalRefs = activeWorkflows.some((wf) =>
328
328
  hasGlobalContextStateRefTransitively(wf.nodes, workflows.customNodes, new Set<string>())
329
329
  )
330
- const needsGlobalContextBridge = hasAuthNodes || hasCurrentUserGlobalRefs
330
+ // Node configs may also reference the logged-in user via a literal
331
+ // {{Current User.id}} template token (resolved at runtime from the
332
+ // __stateValues.currentUser bridge) — those pages need the same
333
+ // GlobalContext plumbing even without a state-get/update node.
334
+ const hasCurrentUserToken = activeWorkflows.some((wf) =>
335
+ wf.nodes.some((n) => JSON.stringify(n.config || {}).includes('{{Current User.'))
336
+ )
337
+ const needsGlobalContextBridge = hasAuthNodes || hasCurrentUserGlobalRefs || hasCurrentUserToken
331
338
 
332
339
  const hasAudioNodes = activeWorkflows.some((wf) =>
333
340
  wf.nodes.some((n) => n.type === 'audio-play' || n.type === 'audio-stop')
@@ -399,6 +406,15 @@ export const createNextWorkflowPlugin: ComponentPluginFactory<WorkflowPluginConf
399
406
  (!pageProtection.allowedRoles || pageProtection.allowedRoles.length === 0)
400
407
  )
401
408
 
409
+ const dynamicRouteAttributeRaw = (uidl as any).outputOptions?.dynamicRouteAttribute as
410
+ | string
411
+ | undefined
412
+ const dynamicRouteAttribute =
413
+ typeof dynamicRouteAttributeRaw === 'string' &&
414
+ /^[A-Za-z_][A-Za-z0-9_-]*$/.test(dynamicRouteAttributeRaw)
415
+ ? dynamicRouteAttributeRaw
416
+ : undefined
417
+
402
418
  const moduleCode = generateModuleLevelCode(
403
419
  activeWorkflows,
404
420
  matchedElementTriggers,
@@ -408,7 +424,8 @@ export const createNextWorkflowPlugin: ComponentPluginFactory<WorkflowPluginConf
408
424
  globalStateChangeWorkflows,
409
425
  hasAudioNodes,
410
426
  needsAdminFormHydration,
411
- isRowOwnedSelfGuardedPage
427
+ isRowOwnedSelfGuardedPage,
428
+ dynamicRouteAttribute
412
429
  )
413
430
 
414
431
  if (moduleCode) {
@@ -558,6 +575,14 @@ export const createNextWorkflowPlugin: ComponentPluginFactory<WorkflowPluginConf
558
575
  path: `${pathPrefix}utils/workflows/runtime`,
559
576
  }
560
577
 
578
+ // Router singleton for {{urlDifferentiator}}-style token resolution in
579
+ // __execWf (route params are only reachable client-side at call time).
580
+ dependencies.Router = {
581
+ type: 'library',
582
+ path: 'next/router',
583
+ version: '12.1.0',
584
+ }
585
+
561
586
  dependencies.workflowClientHandlers = {
562
587
  type: 'local',
563
588
  path: `${pathPrefix}utils/workflows/node-handlers-client`,
@@ -1160,7 +1185,8 @@ const generateModuleLevelCode = (
1160
1185
  globalStateChangeWorkflows: UIDLWorkflow[],
1161
1186
  hasAudioNodes?: boolean,
1162
1187
  includeAdminFormHydrationHelper?: boolean,
1163
- isRowOwnedSelfGuardedPage?: boolean
1188
+ isRowOwnedSelfGuardedPage?: boolean,
1189
+ dynamicRouteAttribute?: string
1164
1190
  ): string => {
1165
1191
  if (allWorkflows.length === 0) {
1166
1192
  return ''
@@ -1451,6 +1477,26 @@ function __createWorkflowHandlers(stateSetters, stateTypes, stateValuesRef) {
1451
1477
  ) {
1452
1478
  value = value.value;
1453
1479
  }
1480
+ // Array-typed states must never receive a non-array payload — one
1481
+ // poisoned write (e.g. a ref resolving to a custom-js node's whole
1482
+ // { result: [...] } return object instead of its array property)
1483
+ // bricks every downstream consumer: the mapper renders nothing and
1484
+ // the next add-row spread throws "is not iterable". Mirrors the
1485
+ // GUI-side guard: unwrap an object with a single array-valued
1486
+ // property, otherwise fall back to an empty array.
1487
+ if (type === 'array' && !Array.isArray(value)) {
1488
+ console.warn(
1489
+ '[workflow] non-array value written to array state "' + property + '"; coercing',
1490
+ value
1491
+ );
1492
+ if (value && typeof value === 'object') {
1493
+ var arrayKeys = Object.keys(value);
1494
+ if (arrayKeys.length === 1 && Array.isArray(value[arrayKeys[0]])) {
1495
+ return value[arrayKeys[0]];
1496
+ }
1497
+ }
1498
+ return [];
1499
+ }
1454
1500
  if (type === 'boolean') return value === 'true' || value === true;
1455
1501
  if (type === 'number' && typeof value === 'string') {
1456
1502
  const parsed = Number(value);
@@ -1552,6 +1598,18 @@ function __createWorkflowHandlers(stateSetters, stateTypes, stateValuesRef) {
1552
1598
 
1553
1599
  function __execWf(config, triggerContext, serverUrls) {
1554
1600
  triggerContext.__stateValues = Object.assign({}, stateValuesRef.current);
1601
+ // Route context for {{urlDifferentiator}} / {{Current Page Entity.id}}
1602
+ // template tokens in node configs — resolved by the shared runtime's
1603
+ // resolveTemplateTokenString. Read at call time (not render time) so the
1604
+ // handlers survive client-side navigation between dynamic routes.
1605
+ if (typeof window !== 'undefined' && Router && Router.router && Router.router.query) {
1606
+ triggerContext.__routeParams = Object.assign({}, Router.router.query);
1607
+ }
1608
+ ${
1609
+ dynamicRouteAttribute
1610
+ ? ` triggerContext.__dynamicRouteParam = '${dynamicRouteAttribute}';\n`
1611
+ : ''
1612
+ }
1555
1613
  var fullConfig = typeof workflowCustomNodes !== 'undefined'
1556
1614
  ? Object.assign({}, config, { customNodes: workflowCustomNodes })
1557
1615
  : config;