Flowfile 0.5.6__py3-none-any.whl → 0.6.1__py3-none-any.whl

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 (256) hide show
  1. flowfile/api.py +8 -6
  2. flowfile/web/static/assets/{AdminView-c2c7942b.js → AdminView-C4K1DdHI.js} +28 -33
  3. flowfile/web/static/assets/{CloudConnectionView-7a3042c6.js → CloudConnectionView-BZbPvPUL.js} +39 -50
  4. flowfile/web/static/assets/{CloudStorageReader-24c54524.css → CloudStorageReader-BDByiqPI.css} +25 -25
  5. flowfile/web/static/assets/{CloudStorageReader-709c4037.js → CloudStorageReader-DLVukNJ7.js} +30 -35
  6. flowfile/web/static/assets/{CloudStorageWriter-604c51a8.js → CloudStorageWriter-Bfi-C1QW.js} +32 -37
  7. flowfile/web/static/assets/{CloudStorageWriter-60547855.css → CloudStorageWriter-y8jL8yjG.css} +24 -24
  8. flowfile/web/static/assets/{ColumnActionInput-d63d6746.js → ColumnActionInput-BpiCApw9.js} +7 -12
  9. flowfile/web/static/assets/{ColumnSelector-0c8cd1cd.js → ColumnSelector-CEAwedI7.js} +1 -2
  10. flowfile/web/static/assets/ContextMenu-CdojQu0w.js +9 -0
  11. flowfile/web/static/assets/ContextMenu-D12mhsy1.js +9 -0
  12. flowfile/web/static/assets/ContextMenu-EWUR98va.js +9 -0
  13. flowfile/web/static/assets/{ContextMenu.vue_vue_type_script_setup_true_lang-774c517c.js → ContextMenu.vue_vue_type_script_setup_true_lang-I4rXXd6G.js} +4 -5
  14. flowfile/web/static/assets/{CrossJoin-38e5b99a.js → CrossJoin-BOFfxkJO.js} +19 -18
  15. flowfile/web/static/assets/{CrossJoin-71b4cc10.css → CrossJoin-Cmbyt9im.css} +18 -18
  16. flowfile/web/static/assets/{CustomNode-76e8f3f5.js → CustomNode-Bhpezobq.js} +12 -17
  17. flowfile/web/static/assets/{DatabaseConnectionSettings-38155669.js → DatabaseConnectionSettings-Dw3bSJKB.js} +10 -11
  18. flowfile/web/static/assets/{DatabaseReader-5bf8c75b.css → DatabaseReader-D6pUNUCs.css} +21 -21
  19. flowfile/web/static/assets/{DatabaseReader-2e549c8f.js → DatabaseReader-m87ghlw0.js} +36 -34
  20. flowfile/web/static/assets/{DatabaseView-dc877c29.js → DatabaseView-CisSAtpe.js} +30 -38
  21. flowfile/web/static/assets/{DatabaseWriter-ffb91864.js → DatabaseWriter-Bbj9JLdL.js} +33 -35
  22. flowfile/web/static/assets/{DatabaseWriter-bdcf2c8b.css → DatabaseWriter-RBqdFLj8.css} +17 -17
  23. flowfile/web/static/assets/{DesignerView-a4466dab.js → DesignerView-DemDevTQ.js} +1752 -2054
  24. flowfile/web/static/assets/{DesignerView-71d4e9a1.css → DesignerView-Dm6OzlIc.css} +209 -168
  25. flowfile/web/static/assets/{DocumentationView-979afc84.js → DocumentationView-BrC1ZR3H.js} +3 -4
  26. flowfile/web/static/assets/{ExploreData-e4b92aaf.js → ExploreData-BMKcDuRb.js} +8 -10
  27. flowfile/web/static/assets/{ExternalSource-d08e7227.js → ExternalSource-BXrNNS-f.js} +40 -42
  28. flowfile/web/static/assets/{ExternalSource-7ac7373f.css → ExternalSource-NB6WVl5R.css} +14 -14
  29. flowfile/web/static/assets/{Filter-7add806d.js → Filter-C2MjsN6P.js} +36 -33
  30. flowfile/web/static/assets/{Filter-7494ea97.css → Filter-DCMGGuGC.css} +9 -9
  31. flowfile/web/static/assets/{Formula-53d58c43.css → Formula-BYafbDj8.css} +4 -4
  32. flowfile/web/static/assets/{Formula-36ab24d2.js → Formula-ufuy4mVD.js} +27 -26
  33. flowfile/web/static/assets/{FuzzyMatch-ad6361d6.css → FuzzyMatch-BGJAwgd0.css} +42 -42
  34. flowfile/web/static/assets/{FuzzyMatch-cc01bb04.js → FuzzyMatch-BOHODq3h.js} +36 -38
  35. flowfile/web/static/assets/{GraphSolver-4fb98f3b.js → GraphSolver-B6ZzpNGO.js} +23 -21
  36. flowfile/web/static/assets/{GraphSolver-4b4d7db9.css → GraphSolver-DFN83sj3.css} +4 -4
  37. flowfile/web/static/assets/{GroupBy-b3c8f429.js → GroupBy-B9BRNcfe.js} +30 -29
  38. flowfile/web/static/assets/{Sort-4abb7fae.css → GroupBy-x4ooP5np.css} +1 -1
  39. flowfile/web/static/assets/Join-Bx_g5bZz.css +118 -0
  40. flowfile/web/static/assets/{Join-096b7b26.js → Join-DsBEy1IH.js} +48 -43
  41. flowfile/web/static/assets/{LoginView-c33a246a.js → LoginView-Ct0rhdcO.js} +1 -2
  42. flowfile/web/static/assets/{ManualInput-39111f19.css → ManualInput-DlZmtMdt.css} +48 -48
  43. flowfile/web/static/assets/{ManualInput-7307e9b1.js → ManualInput-bC4BUgnG.js} +40 -41
  44. flowfile/web/static/assets/{MultiSelect-14822c48.js → MultiSelect-DIQ8PuTC.js} +2 -2
  45. flowfile/web/static/assets/{MultiSelect.vue_vue_type_script_setup_true_lang-90c4d340.js → MultiSelect.vue_vue_type_script_setup_true_lang-BefHfqTI.js} +1 -1
  46. flowfile/web/static/assets/{NodeDesigner-5036c392.js → NodeDesigner-D39yzr2k.js} +178 -208
  47. flowfile/web/static/assets/{NodeDesigner-94cd4dd3.css → NodeDesigner-R0l6sYyY.css} +76 -76
  48. flowfile/web/static/assets/{NumericInput-15cf3b72.js → NumericInput-DMSX3oOr.js} +2 -2
  49. flowfile/web/static/assets/{NumericInput.vue_vue_type_script_setup_true_lang-91e679d7.js → NumericInput.vue_vue_type_script_setup_true_lang-d0YlVHAl.js} +1 -1
  50. flowfile/web/static/assets/{Output-1f8ed42c.js → Output-D0VoXGcW.js} +26 -34
  51. flowfile/web/static/assets/{Output-692dd25d.css → Output-DsmglIDy.css} +5 -5
  52. flowfile/web/static/assets/{Pivot-0e153f4e.js → Pivot-BnMB4sEe.js} +26 -26
  53. flowfile/web/static/assets/{Pivot-0eda81b4.css → Pivot-qKTyWxop.css} +4 -4
  54. flowfile/web/static/assets/{PivotValidation-81ec2a33.js → PivotValidation-B2lWvugt.js} +7 -9
  55. flowfile/web/static/assets/{PivotValidation-5a4f7c79.js → PivotValidation-BPlhRjpL.js} +7 -9
  56. flowfile/web/static/assets/{PolarsCode-a39f15ac.js → PolarsCode-5h0tHnWR.js} +22 -20
  57. flowfile/web/static/assets/{PopOver-ddcfe4f6.js → PopOver-BHpt5rsj.js} +5 -9
  58. flowfile/web/static/assets/{PopOver-d96599db.css → PopOver-CyYM4-rV.css} +1 -1
  59. flowfile/web/static/assets/{Read-90f366bc.css → Read-DJxkrTb_.css} +10 -10
  60. flowfile/web/static/assets/Read-TsLEFh3B.js +227 -0
  61. flowfile/web/static/assets/{RecordCount-e9048ccd.js → RecordCount-DkVixq9v.js} +18 -17
  62. flowfile/web/static/assets/{RecordId-ad02521d.js → RecordId-C2UEGlCf.js} +42 -39
  63. flowfile/web/static/assets/{SQLQueryComponent-2eeecf0b.js → SQLQueryComponent-Dr5KMoD3.js} +2 -3
  64. flowfile/web/static/assets/{Sample-9a68c23d.js → Sample-Cb3eQNmd.js} +30 -30
  65. flowfile/web/static/assets/{SecretSelector-2429f35a.js → SecretSelector-De2L2bSx.js} +3 -4
  66. flowfile/web/static/assets/{SecretsView-c6afc915.js → SecretsView-CheC9BPV.js} +13 -16
  67. flowfile/web/static/assets/{Select-fcd002b6.js → Select-CI8TloRs.js} +41 -36
  68. flowfile/web/static/assets/{SettingsSection-5ce15962.js → SettingsSection-B39ulIiI.js} +1 -2
  69. flowfile/web/static/assets/{SettingsSection-c6b1362c.js → SettingsSection-BiCc7S9h.js} +1 -2
  70. flowfile/web/static/assets/{SettingsSection-cebb91d5.js → SettingsSection-CITK_R7o.js} +2 -3
  71. flowfile/web/static/assets/{SettingsSection-26fe48d4.css → SettingsSection-D2GgY-Aq.css} +4 -4
  72. flowfile/web/static/assets/{SetupView-2d12e01f.js → SetupView-C1aXRDvp.js} +1 -2
  73. flowfile/web/static/assets/{SingleSelect-b67de4eb.js → SingleSelect-Kr_hz90m.js} +2 -2
  74. flowfile/web/static/assets/{SingleSelect.vue_vue_type_script_setup_true_lang-eedb70eb.js → SingleSelect.vue_vue_type_script_setup_true_lang-Rxht5Z5N.js} +1 -1
  75. flowfile/web/static/assets/{SliderInput-fd8134ac.js → SliderInput-CLqpCxCb.js} +1 -2
  76. flowfile/web/static/assets/{GroupBy-5792782d.css → Sort-BIt2kc_p.css} +1 -1
  77. flowfile/web/static/assets/{Sort-c005a573.js → Sort-Dnw_J6Qi.js} +25 -25
  78. flowfile/web/static/assets/{TextInput-1bb31dab.js → TextInput-wdlunIZC.js} +2 -2
  79. flowfile/web/static/assets/{TextInput.vue_vue_type_script_setup_true_lang-a51fe730.js → TextInput.vue_vue_type_script_setup_true_lang-Bcj3ywzv.js} +1 -1
  80. flowfile/web/static/assets/{TextToRows-4f363753.js → TextToRows-BhtyGWPq.js} +42 -49
  81. flowfile/web/static/assets/{TextToRows-12afb4f4.css → TextToRows-DivDOLDx.css} +9 -9
  82. flowfile/web/static/assets/{ToggleSwitch-ca0f2e5e.js → ToggleSwitch-B-6WzfFf.js} +2 -2
  83. flowfile/web/static/assets/{ToggleSwitch.vue_vue_type_script_setup_true_lang-49aa41d8.js → ToggleSwitch.vue_vue_type_script_setup_true_lang-Cj8LqT-b.js} +1 -1
  84. flowfile/web/static/assets/{UnavailableFields-f6147968.js → UnavailableFields-Yf6XSqFB.js} +2 -3
  85. flowfile/web/static/assets/{Union-c65f17b7.js → Union-CwpjeKYC.js} +20 -23
  86. flowfile/web/static/assets/{Unpivot-b6ad6427.css → Union-DQJcpp3-.css} +6 -6
  87. flowfile/web/static/assets/{Unique-a1d96fb2.js → Unique-25v3urqH.js} +75 -74
  88. flowfile/web/static/assets/{Union-d6a8d7d5.css → Unpivot-Deqh1gtI.css} +6 -6
  89. flowfile/web/static/assets/{Unpivot-c2657ff3.js → Unpivot-sYcTTXrq.js} +34 -27
  90. flowfile/web/static/assets/{UnpivotValidation-28e29a3b.js → UnpivotValidation-C5DDEKY2.js} +5 -7
  91. flowfile/web/static/assets/VueGraphicWalker-B8l1_Z92.js +131 -0
  92. flowfile/web/static/assets/VueGraphicWalker-Da_1-3me.css +21 -0
  93. flowfile/web/static/assets/{api-df48ec50.js → api-C0LvF-0C.js} +1 -1
  94. flowfile/web/static/assets/{api-ee542cf7.js → api-DaC83EO_.js} +1 -1
  95. flowfile/web/static/assets/client-C8Ygr6Gb.js +42 -0
  96. flowfile/web/static/assets/{dropDown-7576a76a.js → dropDown-D5YXaPRR.js} +7 -12
  97. flowfile/web/static/assets/{fullEditor-7583bef5.js → fullEditor-BVYnWm05.js} +300 -18
  98. flowfile/web/static/assets/genericNodeSettings-2wAu-QKn.css +75 -0
  99. flowfile/web/static/assets/genericNodeSettings-BBtW_Cpz.js +590 -0
  100. flowfile/web/static/assets/{VueGraphicWalker-2fc3ddd4.js → graphic-walker.es-VrK6vdGE.js} +92305 -89751
  101. flowfile/web/static/assets/index-BCJxPfM5.js +6693 -0
  102. flowfile/web/static/assets/{index-057d770d.js → index-CHPMUR0d.js} +150 -170
  103. flowfile/web/static/assets/index-DPkoZWq8.js +32 -0
  104. flowfile/web/static/assets/index-DnW_KC_I.js +277 -0
  105. flowfile/web/static/assets/index-UFXyfirV.css +10797 -0
  106. flowfile/web/static/assets/index-bcuE0Z0p.js +87456 -0
  107. flowfile/web/static/assets/{node.types-2c15bb7e.js → node.types-Dl4gtSW9.js} +2 -2
  108. flowfile/web/static/assets/{outputCsv-c492b15e.js → outputCsv-BELuBiJZ.js} +1 -2
  109. flowfile/web/static/assets/outputCsv-CdGkv-fN.css +2581 -0
  110. flowfile/web/static/assets/{outputExcel-13bfa10f.js → outputExcel-D0TTNM79.js} +1 -2
  111. flowfile/web/static/assets/{outputParquet-9be1523a.js → outputParquet-Cz9EbRHj.js} +1 -2
  112. flowfile/web/static/assets/{readCsv-5a49a8c9.js → readCsv-7bd3kUMI.js} +1 -2
  113. flowfile/web/static/assets/{readExcel-27c30ad8.js → readExcel-Cq8CCwIv.js} +3 -4
  114. flowfile/web/static/assets/{readParquet-c5244ad5.css → readParquet-CRDmBrsp.css} +4 -4
  115. flowfile/web/static/assets/{readParquet-446bde68.js → readParquet-DjR4mRaj.js} +4 -5
  116. flowfile/web/static/assets/{secrets.api-34431884.js → secrets.api-C9o2KE5V.js} +1 -1
  117. flowfile/web/static/assets/{selectDynamic-5754a2b1.js → selectDynamic-Bl5FVsME.js} +5 -7
  118. flowfile/web/static/assets/useNodeSettings-dMS9zmh_.js +69 -0
  119. flowfile/web/static/assets/{vue-codemirror.esm-8f46fb36.js → vue-codemirror.esm-CwaYwln0.js} +3469 -3064
  120. flowfile/web/static/assets/{vue-content-loader.es-808fe33a.js → vue-content-loader.es-CMoRXo7N.js} +3 -3
  121. flowfile/web/static/index.html +2 -3
  122. {flowfile-0.5.6.dist-info → flowfile-0.6.1.dist-info}/METADATA +2 -1
  123. flowfile-0.6.1.dist-info/RECORD +417 -0
  124. {flowfile-0.5.6.dist-info → flowfile-0.6.1.dist-info}/WHEEL +1 -1
  125. flowfile_core/auth/password.py +1 -0
  126. flowfile_core/database/init_db.py +7 -5
  127. flowfile_core/fileExplorer/funcs.py +2 -2
  128. flowfile_core/flowfile/code_generator/code_generator.py +13 -11
  129. flowfile_core/flowfile/filter_expressions.py +327 -0
  130. flowfile_core/flowfile/flow_data_engine/flow_data_engine.py +61 -59
  131. flowfile_core/flowfile/flow_data_engine/flow_file_column/type_registry.py +3 -29
  132. flowfile_core/flowfile/flow_data_engine/flow_file_column/utils.py +45 -14
  133. flowfile_core/flowfile/flow_data_engine/subprocess_operations/models.py +20 -3
  134. flowfile_core/flowfile/flow_data_engine/subprocess_operations/streaming.py +206 -0
  135. flowfile_core/flowfile/flow_data_engine/subprocess_operations/subprocess_operations.py +146 -24
  136. flowfile_core/flowfile/flow_graph.py +504 -190
  137. flowfile_core/flowfile/flow_node/__init__.py +32 -0
  138. flowfile_core/flowfile/flow_node/executor.py +404 -0
  139. flowfile_core/flowfile/flow_node/flow_node.py +207 -106
  140. flowfile_core/flowfile/flow_node/models.py +40 -0
  141. flowfile_core/flowfile/flow_node/output_field_config_applier.py +217 -0
  142. flowfile_core/flowfile/flow_node/schema_utils.py +78 -0
  143. flowfile_core/flowfile/flow_node/state.py +155 -0
  144. flowfile_core/flowfile/history_manager.py +401 -0
  145. flowfile_core/flowfile/manage/compatibility_enhancements.py +9 -0
  146. flowfile_core/flowfile/manage/io_flowfile.py +3 -1
  147. flowfile_core/flowfile/sources/external_sources/sql_source/models.py +20 -4
  148. flowfile_core/flowfile/util/execution_orderer.py +89 -36
  149. flowfile_core/routes/auth.py +8 -9
  150. flowfile_core/routes/routes.py +320 -101
  151. flowfile_core/routes/user_defined_components.py +18 -16
  152. flowfile_core/schemas/history_schema.py +220 -0
  153. flowfile_core/schemas/input_schema.py +130 -6
  154. flowfile_core/schemas/schemas.py +9 -0
  155. flowfile_core/schemas/transform_schema.py +27 -5
  156. flowfile_core/schemas/yaml_types.py +23 -5
  157. flowfile_frame/adding_expr.py +18 -126
  158. flowfile_frame/callable_utils.py +261 -0
  159. flowfile_frame/database/connection_manager.py +0 -1
  160. flowfile_frame/expr.py +8 -4
  161. flowfile_frame/flow_frame.py +41 -41
  162. flowfile_frame/lazy.py +3 -12
  163. flowfile_frame/lazy_methods.py +5 -64
  164. flowfile_frame/utils.py +13 -32
  165. flowfile_worker/funcs.py +6 -4
  166. flowfile_worker/main.py +2 -0
  167. flowfile_worker/models.py +31 -11
  168. flowfile_worker/routes.py +60 -35
  169. flowfile_worker/spawner.py +7 -1
  170. flowfile_worker/streaming.py +335 -0
  171. flowfile/web/static/assets/ContextMenu-366bf1b4.js +0 -9
  172. flowfile/web/static/assets/ContextMenu-85cf5b44.js +0 -9
  173. flowfile/web/static/assets/ContextMenu-9d28ae6d.js +0 -9
  174. flowfile/web/static/assets/Join-28b5e18f.css +0 -109
  175. flowfile/web/static/assets/Read-39b63932.js +0 -222
  176. flowfile/web/static/assets/VueGraphicWalker-430f0b86.css +0 -6
  177. flowfile/web/static/assets/database_reader-ce1e55f3.svg +0 -24
  178. flowfile/web/static/assets/database_writer-b4ad0753.svg +0 -23
  179. flowfile/web/static/assets/element-icons-9c88a535.woff +0 -0
  180. flowfile/web/static/assets/element-icons-de5eb258.ttf +0 -0
  181. flowfile/web/static/assets/genericNodeSettings-0155288b.js +0 -136
  182. flowfile/web/static/assets/genericNodeSettings-3b2507ea.css +0 -46
  183. flowfile/web/static/assets/index-aeec439d.js +0 -38
  184. flowfile/web/static/assets/index-ca6799de.js +0 -62760
  185. flowfile/web/static/assets/index-d60c9dd4.css +0 -10777
  186. flowfile/web/static/assets/nodeInput-d478b9ac.js +0 -2
  187. flowfile/web/static/assets/outputCsv-cc84e09f.css +0 -2499
  188. flowfile-0.5.6.dist-info/RECORD +0 -407
  189. /flowfile/web/static/assets/{AdminView-f53bad23.css → AdminView-B2Dthl3u.css} +0 -0
  190. /flowfile/web/static/assets/{CloudConnectionView-cf85f943.css → CloudConnectionView-BdFYGWV7.css} +0 -0
  191. /flowfile/web/static/assets/{ColumnActionInput-c44b7aee.css → ColumnActionInput-dCasSIC9.css} +0 -0
  192. /flowfile/web/static/assets/{ColumnSelector-371637fb.css → ColumnSelector-j6sEOjo1.css} +0 -0
  193. /flowfile/web/static/assets/{CustomNode-edb9b939.css → CustomNode-VPlajG0j.css} +0 -0
  194. /flowfile/web/static/assets/{DatabaseConnectionSettings-c20a1e16.css → DatabaseConnectionSettings-B78hXYgu.css} +0 -0
  195. /flowfile/web/static/assets/{DatabaseView-6655afd6.css → DatabaseView-B-_adk1s.css} +0 -0
  196. /flowfile/web/static/assets/{DocumentationView-9ea6e871.css → DocumentationView-CL7iipFL.css} +0 -0
  197. /flowfile/web/static/assets/{ExploreData-10c5acc8.css → ExploreData-DHjv0Plr.css} +0 -0
  198. /flowfile/web/static/assets/{LoginView-d325d632.css → LoginView-DN1BXY3e.css} +0 -0
  199. /flowfile/web/static/assets/{PivotValidation-0e905b1a.css → PivotValidation-DK-FARWe.css} +0 -0
  200. /flowfile/web/static/assets/{PivotValidation-41b57ad6.css → PivotValidation-FUa9F47u.css} +0 -0
  201. /flowfile/web/static/assets/{PolarsCode-2b1f1f23.css → PolarsCode-G-gRSrSc.css} +0 -0
  202. /flowfile/web/static/assets/{SQLQueryComponent-edb90b98.css → SQLQueryComponent-oAbWw0r-.css} +0 -0
  203. /flowfile/web/static/assets/{SecretSelector-6329f743.css → SecretSelector-CJSadIZx.css} +0 -0
  204. /flowfile/web/static/assets/{SecretsView-aa291340.css → SecretsView-DbzIRAba.css} +0 -0
  205. /flowfile/web/static/assets/{SettingsSection-8f980839.css → SettingsSection-BGcJnH6q.css} +0 -0
  206. /flowfile/web/static/assets/{SettingsSection-07fbbc39.css → SettingsSection-DDWn_EGW.css} +0 -0
  207. /flowfile/web/static/assets/{SetupView-ec26f76a.css → SetupView-CI1nd-5Z.css} +0 -0
  208. /flowfile/web/static/assets/{SliderInput-f2e4f23c.css → SliderInput-BRk-q_Dk.css} +0 -0
  209. /flowfile/web/static/assets/{UnavailableFields-394a1f78.css → UnavailableFields-DRKDImKe.css} +0 -0
  210. /flowfile/web/static/assets/{Unique-2b705521.css → Unique-Absb0aON.css} +0 -0
  211. /flowfile/web/static/assets/{UnpivotValidation-d5ca3b7b.css → UnpivotValidation-DSBkFgS-.css} +0 -0
  212. /flowfile/web/static/assets/{airbyte-292aa232.png → airbyte-W0xvIXwZ.png} +0 -0
  213. /flowfile/web/static/assets/{cloud_storage_reader-aa1415d6.png → cloud_storage_reader-3GpSCk90.png} +0 -0
  214. /flowfile/web/static/assets/{cross_join-d30c0290.png → cross_join-B0qpgYoV.png} +0 -0
  215. /flowfile/web/static/assets/{dropDown-1d6acbd9.css → dropDown-CE0VF5_P.css} +0 -0
  216. /flowfile/web/static/assets/{explore_data-8a0a2861.png → explore_data-tX6olPPL.png} +0 -0
  217. /flowfile/web/static/assets/{fa-brands-400-808443ae.ttf → fa-brands-400-D1LuMI3I.ttf} +0 -0
  218. /flowfile/web/static/assets/{fa-brands-400-d7236a19.woff2 → fa-brands-400-D_cYUPeE.woff2} +0 -0
  219. /flowfile/web/static/assets/{fa-regular-400-e3456d12.woff2 → fa-regular-400-BjRzuEpd.woff2} +0 -0
  220. /flowfile/web/static/assets/{fa-regular-400-54cf6086.ttf → fa-regular-400-DZaxPHgR.ttf} +0 -0
  221. /flowfile/web/static/assets/{fa-solid-900-aa759986.woff2 → fa-solid-900-CTAAxXor.woff2} +0 -0
  222. /flowfile/web/static/assets/{fa-solid-900-d2f05935.ttf → fa-solid-900-D0aA9rwL.ttf} +0 -0
  223. /flowfile/web/static/assets/{fa-v4compatibility-0ce9033c.woff2 → fa-v4compatibility-C9RhG_FT.woff2} +0 -0
  224. /flowfile/web/static/assets/{fa-v4compatibility-30f6abf6.ttf → fa-v4compatibility-CCth-dXg.ttf} +0 -0
  225. /flowfile/web/static/assets/{filter-d7708bda.png → filter-WRdZyUOw.png} +0 -0
  226. /flowfile/web/static/assets/{formula-eeeb1611.png → formula-CgM7uHVI.png} +0 -0
  227. /flowfile/web/static/assets/{fullEditor-fe9f7e18.css → fullEditor-CmDI7T9F.css} +0 -0
  228. /flowfile/web/static/assets/{fuzzy_match-40c161b2.png → fuzzy_match-Yon3k5Tc.png} +0 -0
  229. /flowfile/web/static/assets/{graph_solver-8b7888b8.png → graph_solver-BlMrBttD.png} +0 -0
  230. /flowfile/web/static/assets/{group_by-80561fc3.png → group_by-Gici0CSS.png} +0 -0
  231. /flowfile/web/static/assets/{input_data-ab2eb678.png → input_data-BRdGecLc.png} +0 -0
  232. /flowfile/web/static/assets/{join-349043ae.png → join-BITWRu73.png} +0 -0
  233. /flowfile/web/static/assets/{manual_input-ae98f31d.png → manual_input-CFvo_EUS.png} +0 -0
  234. /flowfile/web/static/assets/{old_join-5d0eb604.png → old_join-B9bkpPqv.png} +0 -0
  235. /flowfile/web/static/assets/{output-06ec0371.png → output-Dp7-ZpC4.png} +0 -0
  236. /flowfile/web/static/assets/{outputExcel-f5d272b2.css → outputExcel-CKgRe2iT.css} +0 -0
  237. /flowfile/web/static/assets/{outputParquet-54597c3c.css → outputParquet-d7j407cK.css} +0 -0
  238. /flowfile/web/static/assets/{pivot-9660df51.png → pivot-DSxKhNlD.png} +0 -0
  239. /flowfile/web/static/assets/{polars_code-05ce5dc6.png → polars_code-DxiztZ1c.png} +0 -0
  240. /flowfile/web/static/assets/{readCsv-3bfac4c3.css → readCsv-BG-1Jilp.css} +0 -0
  241. /flowfile/web/static/assets/{readExcel-3db6b763.css → readExcel-DBQXKPtC.css} +0 -0
  242. /flowfile/web/static/assets/{record_count-dab44eb5.png → record_count-DCeaLtpS.png} +0 -0
  243. /flowfile/web/static/assets/{record_id-0b15856b.png → record_id-FeUjyIFh.png} +0 -0
  244. /flowfile/web/static/assets/{sample-693a88b5.png → sample-DeqfRiB-.png} +0 -0
  245. /flowfile/web/static/assets/{select-b0d0437a.png → select-D4JjbdjS.png} +0 -0
  246. /flowfile/web/static/assets/{selectDynamic-f2fb394f.css → selectDynamic-CjeTPUUo.css} +0 -0
  247. /flowfile/web/static/assets/{sort-2aa579f0.png → sort-DGwUG9WS.png} +0 -0
  248. /flowfile/web/static/assets/{summarize-2a099231.png → summarize-DFaNHpfp.png} +0 -0
  249. /flowfile/web/static/assets/{text_to_rows-859b29ea.png → text_to_rows-BdiAewrN.png} +0 -0
  250. /flowfile/web/static/assets/{union-2d8609f4.png → union-DCK-LSMq.png} +0 -0
  251. /flowfile/web/static/assets/{unique-1958b98a.png → unique-CdP3zZIq.png} +0 -0
  252. /flowfile/web/static/assets/{unpivot-d3cb4b5b.png → unpivot-CHttrEt8.png} +0 -0
  253. /flowfile/web/static/assets/{user-defined-icon-0ae16c90.png → user-defined-icon-BcIp2Vzo.png} +0 -0
  254. /flowfile/web/static/assets/{view-7a0f0be1.png → view-DUSRwjvq.png} +0 -0
  255. {flowfile-0.5.6.dist-info → flowfile-0.6.1.dist-info}/entry_points.txt +0 -0
  256. {flowfile-0.5.6.dist-info → flowfile-0.6.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,23 +1,25 @@
1
1
 
2
2
  import ast
3
3
  import re
4
- from typing import Dict, Any, List, Optional
5
4
  from pathlib import Path
5
+ from typing import Any
6
6
 
7
- from fastapi import APIRouter, HTTPException, Depends, UploadFile, File
7
+ from fastapi import APIRouter, Depends, File, HTTPException, UploadFile
8
8
  from fastapi.responses import FileResponse
9
9
  from pydantic import BaseModel
10
10
 
11
11
  from flowfile_core import flow_file_handler
12
+
12
13
  # Core modules
13
14
  from flowfile_core.auth.jwt import get_current_active_user
14
15
  from flowfile_core.configs import logger
15
16
  from flowfile_core.configs.node_store import (
16
17
  CUSTOM_NODE_STORE,
17
18
  add_to_custom_node_store,
18
- remove_from_custom_node_store,
19
19
  load_single_node_from_file,
20
+ remove_from_custom_node_store,
20
21
  )
22
+
21
23
  # File handling
22
24
  from flowfile_core.schemas import input_schema
23
25
  from flowfile_core.utils.utils import camel_case_to_snake_case
@@ -66,7 +68,7 @@ def get_simple_custom_object(flow_id: int, node_id: int):
66
68
 
67
69
 
68
70
  @router.post("/update_user_defined_node", tags=["transform"])
69
- def update_user_defined_node(input_data: Dict[str, Any], node_type: str, current_user=Depends(get_current_active_user)):
71
+ def update_user_defined_node(input_data: dict[str, Any], node_type: str, current_user=Depends(get_current_active_user)):
70
72
  input_data['user_id'] = current_user.id
71
73
  node_type = camel_case_to_snake_case(node_type)
72
74
  flow_id = int(input_data.get('flow_id'))
@@ -149,7 +151,7 @@ def _extract_node_info_from_file(file_path: Path) -> CustomNodeInfo:
149
151
  info = CustomNodeInfo(file_name=file_path.name)
150
152
 
151
153
  try:
152
- with open(file_path, 'r', encoding='utf-8') as f:
154
+ with open(file_path, encoding='utf-8') as f:
153
155
  content = f.read()
154
156
 
155
157
  tree = ast.parse(content)
@@ -199,14 +201,14 @@ def _extract_node_info_from_file(file_path: Path) -> CustomNodeInfo:
199
201
  return info
200
202
 
201
203
 
202
- @router.get("/list-custom-nodes", summary="List all custom nodes", response_model=List[CustomNodeInfo])
203
- def list_custom_nodes() -> List[CustomNodeInfo]:
204
+ @router.get("/list-custom-nodes", summary="List all custom nodes", response_model=list[CustomNodeInfo])
205
+ def list_custom_nodes() -> list[CustomNodeInfo]:
204
206
  """
205
207
  List all custom node Python files in the user-defined nodes directory.
206
208
  Returns basic metadata extracted from each file.
207
209
  """
208
210
  nodes_dir = storage.user_defined_nodes_directory
209
- nodes: List[CustomNodeInfo] = []
211
+ nodes: list[CustomNodeInfo] = []
210
212
 
211
213
  if not nodes_dir.exists():
212
214
  return nodes
@@ -223,7 +225,7 @@ def list_custom_nodes() -> List[CustomNodeInfo]:
223
225
 
224
226
 
225
227
  @router.get("/get-custom-node/{file_name}", summary="Get custom node details")
226
- def get_custom_node(file_name: str) -> Dict[str, Any]:
228
+ def get_custom_node(file_name: str) -> dict[str, Any]:
227
229
  """
228
230
  Get the full content and parsed metadata of a custom node file.
229
231
  This endpoint is used by the Node Designer to load an existing node for editing.
@@ -239,7 +241,7 @@ def get_custom_node(file_name: str) -> Dict[str, Any]:
239
241
  raise HTTPException(status_code=404, detail=f"Node file '{safe_name}' not found")
240
242
 
241
243
  try:
242
- with open(file_path, 'r', encoding='utf-8') as f:
244
+ with open(file_path, encoding='utf-8') as f:
243
245
  content = f.read()
244
246
  except Exception as e:
245
247
  raise HTTPException(status_code=500, detail=f"Failed to read file: {str(e)}")
@@ -320,7 +322,7 @@ def get_custom_node(file_name: str) -> Dict[str, Any]:
320
322
 
321
323
 
322
324
  @router.delete("/delete-custom-node/{file_name}", summary="Delete a custom node")
323
- def delete_custom_node(file_name: str) -> Dict[str, Any]:
325
+ def delete_custom_node(file_name: str) -> dict[str, Any]:
324
326
  """
325
327
  Delete a custom node Python file from the user-defined nodes directory.
326
328
  This also attempts to unregister the node from the node store.
@@ -384,14 +386,14 @@ ALLOWED_ICON_EXTENSIONS = {'.png', '.jpg', '.jpeg', '.svg', '.gif', '.webp'}
384
386
  MAX_ICON_SIZE = 5 * 1024 * 1024 # 5MB
385
387
 
386
388
 
387
- @router.get("/list-icons", summary="List all available icons", response_model=List[IconInfo])
388
- def list_icons() -> List[IconInfo]:
389
+ @router.get("/list-icons", summary="List all available icons", response_model=list[IconInfo])
390
+ def list_icons() -> list[IconInfo]:
389
391
  """
390
392
  List all icon files available for custom nodes.
391
393
  Returns icons from the user_defined_nodes/icons directory.
392
394
  """
393
395
  icons_dir = storage.user_defined_nodes_icons
394
- icons: List[IconInfo] = []
396
+ icons: list[IconInfo] = []
395
397
 
396
398
  if not icons_dir.exists():
397
399
  return icons
@@ -406,7 +408,7 @@ def list_icons() -> List[IconInfo]:
406
408
 
407
409
 
408
410
  @router.post("/upload-icon", summary="Upload a custom icon")
409
- async def upload_icon(file: UploadFile = File(...)) -> Dict[str, Any]:
411
+ async def upload_icon(file: UploadFile = File(...)) -> dict[str, Any]:
410
412
  """
411
413
  Upload a new icon file to the user_defined_nodes/icons directory.
412
414
 
@@ -507,7 +509,7 @@ def get_icon(file_name: str) -> FileResponse:
507
509
 
508
510
 
509
511
  @router.delete("/delete-icon/{file_name}", summary="Delete a custom icon")
510
- def delete_icon(file_name: str) -> Dict[str, Any]:
512
+ def delete_icon(file_name: str) -> dict[str, Any]:
511
513
  """
512
514
  Delete a custom icon file from the icons directory.
513
515
  """
@@ -0,0 +1,220 @@
1
+ """
2
+ Schema definitions for the undo/redo history system.
3
+
4
+ This module defines the Pydantic models for tracking flow graph history,
5
+ enabling users to undo and redo changes to their flow graphs.
6
+ """
7
+
8
+ import pickle
9
+ import zlib
10
+ from enum import Enum
11
+ from typing import TYPE_CHECKING
12
+
13
+ from pydantic import BaseModel, Field
14
+
15
+ if TYPE_CHECKING:
16
+ pass
17
+
18
+
19
+ class HistoryActionType(str, Enum):
20
+ """Enumeration of action types that can be tracked in history."""
21
+
22
+ ADD_NODE = "add_node"
23
+ DELETE_NODE = "delete_node"
24
+ MOVE_NODE = "move_node"
25
+ ADD_CONNECTION = "add_connection"
26
+ DELETE_CONNECTION = "delete_connection"
27
+ UPDATE_SETTINGS = "update_settings"
28
+ COPY_NODE = "copy_node"
29
+ PASTE_NODES = "paste_nodes"
30
+ APPLY_LAYOUT = "apply_layout"
31
+ BATCH = "batch"
32
+
33
+
34
+ class HistoryConfig(BaseModel):
35
+ """Configuration for the history system."""
36
+
37
+ enabled: bool = Field(default=True, description="Whether history tracking is enabled")
38
+ max_stack_size: int = Field(default=50, description="Maximum number of history entries to keep")
39
+ use_compression: bool = Field(default=True, description="Whether to compress snapshots")
40
+ compression_level: int = Field(default=6, ge=1, le=9, description="Compression level (1-9)")
41
+
42
+
43
+ class CompressedSnapshot:
44
+ """Efficiently stores a compressed flow state snapshot.
45
+
46
+ Uses zlib compression to reduce memory usage by 60-80%.
47
+ This is not a Pydantic model to avoid serialization overhead.
48
+ """
49
+
50
+ __slots__ = ('_compressed_data', '_hash')
51
+
52
+ def __init__(self, snapshot_dict: dict, compression_level: int = 6):
53
+ """Create a compressed snapshot from a dictionary.
54
+
55
+ Args:
56
+ snapshot_dict: The flow state dictionary to compress.
57
+ compression_level: Compression level 1-9 (higher = smaller but slower).
58
+ """
59
+ # Pickle and compress the snapshot
60
+ pickled = pickle.dumps(snapshot_dict, protocol=pickle.HIGHEST_PROTOCOL)
61
+ self._compressed_data = zlib.compress(pickled, level=compression_level)
62
+
63
+ # Pre-compute hash for fast comparison
64
+ self._hash = self._compute_hash(snapshot_dict)
65
+
66
+ @staticmethod
67
+ def _compute_hash(snapshot_dict: dict) -> int:
68
+ """Compute a fast structural hash of the snapshot."""
69
+ nodes = snapshot_dict.get("nodes", [])
70
+
71
+ # Build tuple of node signatures for hashing
72
+ node_signatures = []
73
+ for n in sorted(nodes, key=lambda x: x.get("id", 0)):
74
+ sig = (
75
+ n.get("id"),
76
+ n.get("type"),
77
+ tuple(n.get("input_ids") or []),
78
+ n.get("left_input_id"),
79
+ n.get("right_input_id"),
80
+ tuple(n.get("outputs") or []),
81
+ n.get("x_position"),
82
+ n.get("y_position"),
83
+ # Include a hash of setting_input for change detection
84
+ hash(str(n.get("setting_input"))) if n.get("setting_input") else None,
85
+ )
86
+ node_signatures.append(sig)
87
+
88
+ settings = snapshot_dict.get("flowfile_settings", {})
89
+ settings_tuple = tuple(sorted(settings.items())) if isinstance(settings, dict) else hash(str(settings))
90
+
91
+ return hash((
92
+ snapshot_dict.get("flowfile_id"),
93
+ settings_tuple,
94
+ tuple(node_signatures),
95
+ ))
96
+
97
+ def decompress(self) -> dict:
98
+ """Decompress and return the original snapshot dictionary."""
99
+ pickled = zlib.decompress(self._compressed_data)
100
+ return pickle.loads(pickled)
101
+
102
+ @property
103
+ def hash(self) -> int:
104
+ """Get the pre-computed hash for fast comparison."""
105
+ return self._hash
106
+
107
+ @property
108
+ def compressed_size(self) -> int:
109
+ """Get the size of the compressed data in bytes."""
110
+ return len(self._compressed_data)
111
+
112
+ def __eq__(self, other: "CompressedSnapshot") -> bool:
113
+ """Fast equality check using pre-computed hashes."""
114
+ if not isinstance(other, CompressedSnapshot):
115
+ return False
116
+ return self._hash == other._hash
117
+
118
+
119
+ class HistoryEntry:
120
+ """A single entry in the history stack.
121
+
122
+ Stores a compressed snapshot of the flow state along with metadata
123
+ about the action that created this entry.
124
+
125
+ Uses __slots__ for memory efficiency.
126
+ """
127
+
128
+ __slots__ = ('_snapshot', 'action_type', 'description', 'timestamp', 'node_id')
129
+
130
+ def __init__(
131
+ self,
132
+ snapshot: CompressedSnapshot,
133
+ action_type: HistoryActionType,
134
+ description: str,
135
+ timestamp: float,
136
+ node_id: int | None = None,
137
+ ):
138
+ self._snapshot = snapshot
139
+ self.action_type = action_type
140
+ self.description = description
141
+ self.timestamp = timestamp
142
+ self.node_id = node_id
143
+
144
+ @classmethod
145
+ def from_dict(
146
+ cls,
147
+ snapshot_dict: dict,
148
+ action_type: HistoryActionType,
149
+ description: str,
150
+ timestamp: float,
151
+ node_id: int | None = None,
152
+ compression_level: int = 6,
153
+ ) -> "HistoryEntry":
154
+ """Create a HistoryEntry from a snapshot dictionary.
155
+
156
+ Args:
157
+ snapshot_dict: The flow state dictionary.
158
+ action_type: The type of action.
159
+ description: Human-readable description.
160
+ timestamp: Unix timestamp.
161
+ node_id: Optional affected node ID.
162
+ compression_level: Compression level 1-9.
163
+ """
164
+ compressed = CompressedSnapshot(snapshot_dict, compression_level)
165
+ return cls(compressed, action_type, description, timestamp, node_id)
166
+
167
+ def get_snapshot(self) -> dict:
168
+ """Decompress and return the snapshot dictionary."""
169
+ return self._snapshot.decompress()
170
+
171
+ @property
172
+ def snapshot_hash(self) -> int:
173
+ """Get the hash of the snapshot for comparison."""
174
+ return self._snapshot.hash
175
+
176
+ @property
177
+ def compressed_size(self) -> int:
178
+ """Get the compressed size in bytes."""
179
+ return self._snapshot.compressed_size
180
+
181
+
182
+ class HistoryState(BaseModel):
183
+ """Current state of the history system.
184
+
185
+ Provides information about what undo/redo operations are available.
186
+ """
187
+
188
+ can_undo: bool = Field(default=False, description="Whether undo is available")
189
+ can_redo: bool = Field(default=False, description="Whether redo is available")
190
+ undo_description: str | None = Field(
191
+ default=None, description="Description of the action that would be undone"
192
+ )
193
+ redo_description: str | None = Field(
194
+ default=None, description="Description of the action that would be redone"
195
+ )
196
+ undo_count: int = Field(default=0, description="Number of available undo steps")
197
+ redo_count: int = Field(default=0, description="Number of available redo steps")
198
+
199
+
200
+ class UndoRedoResult(BaseModel):
201
+ """Result of an undo or redo operation."""
202
+
203
+ success: bool = Field(..., description="Whether the operation succeeded")
204
+ action_description: str | None = Field(
205
+ default=None, description="Description of the action that was undone/redone"
206
+ )
207
+ error_message: str | None = Field(
208
+ default=None, description="Error message if the operation failed"
209
+ )
210
+
211
+
212
+ class OperationResponse(BaseModel):
213
+ """Standard response for operations that modify the flow graph.
214
+
215
+ Includes the current history state so the frontend can update its UI.
216
+ """
217
+
218
+ success: bool = Field(default=True, description="Whether the operation succeeded")
219
+ message: str | None = Field(default=None, description="Optional message")
220
+ history: HistoryState = Field(..., description="Current history state after the operation")
@@ -25,6 +25,7 @@ from flowfile_core.schemas.yaml_types import (
25
25
  NodeSelectYaml,
26
26
  OutputSettingsYaml,
27
27
  )
28
+ from flowfile_core.types import DataTypeStr
28
29
  from flowfile_core.utils.utils import ensure_similarity_dicts, standardize_col_dtype
29
30
 
30
31
  SecretRef = Annotated[
@@ -80,6 +81,28 @@ class MinimalFieldInfo(BaseModel):
80
81
  data_type: str = "String"
81
82
 
82
83
 
84
+ class OutputFieldInfo(BaseModel):
85
+ """Field information with optional default value for output field configuration."""
86
+
87
+ name: str
88
+ data_type: DataTypeStr = "String"
89
+ default_value: str | None = None # Can be a literal value or expression
90
+
91
+
92
+ class OutputFieldConfig(BaseModel):
93
+ """Configuration for output field validation and transformation behavior."""
94
+
95
+ enabled: bool = False
96
+ validation_mode_behavior: Literal[
97
+ "add_missing", # Add missing fields with defaults, remove extra columns
98
+ "add_missing_keep_extra", # Add missing fields with defaults, keep all incoming columns
99
+ "raise_on_missing", # Raise error if any fields are missing
100
+ "select_only" # Select only specified fields, skip missing silently
101
+ ] = "select_only"
102
+ fields: list[OutputFieldInfo] = Field(default_factory=list)
103
+ validate_data_types: bool = False # Enable data type validation without casting
104
+
105
+
83
106
  class InputTableBase(BaseModel):
84
107
  """Base settings for input file operations."""
85
108
 
@@ -334,9 +357,25 @@ class NodeBase(BaseModel):
334
357
  pos_y: float | None = 0
335
358
  is_setup: bool | None = True
336
359
  description: str | None = ""
360
+ node_reference: str | None = None # Unique reference identifier for code generation (lowercase, no spaces)
337
361
  user_id: int | None = None
338
362
  is_flow_output: bool | None = False
339
363
  is_user_defined: bool | None = False # Indicator if the node is a user defined node
364
+ output_field_config: OutputFieldConfig | None = None
365
+
366
+ @field_validator("node_reference", mode="before")
367
+ @classmethod
368
+ def validate_node_reference(cls, v):
369
+ """Validates that node_reference is lowercase and contains no spaces."""
370
+ if v is None or v == "":
371
+ return None
372
+ if not isinstance(v, str):
373
+ raise ValueError("node_reference must be a string")
374
+ if " " in v:
375
+ raise ValueError("node_reference cannot contain spaces")
376
+ if v != v.lower():
377
+ raise ValueError("node_reference must be lowercase")
378
+ return v
340
379
 
341
380
 
342
381
  class NodeSingleInput(NodeBase):
@@ -360,12 +399,27 @@ class NodeSelect(NodeSingleInput):
360
399
 
361
400
  def to_yaml_dict(self) -> NodeSelectYaml:
362
401
  """Converts the select node settings to a dictionary for YAML serialization."""
363
- return {
364
- "cache_results": self.cache_results,
402
+ result: NodeSelectYaml = {
403
+ "cache_results": bool(self.cache_results),
365
404
  "keep_missing": self.keep_missing,
366
405
  "select_input": [s.to_yaml_dict() for s in self.select_input],
367
406
  "sorted_by": self.sorted_by,
368
407
  }
408
+ if self.output_field_config:
409
+ result["output_field_config"] = {
410
+ "enabled": self.output_field_config.enabled,
411
+ "validation_mode_behavior": self.output_field_config.validation_mode_behavior,
412
+ "validate_data_types": self.output_field_config.validate_data_types,
413
+ "fields": [
414
+ {
415
+ "name": f.name,
416
+ "data_type": f.data_type,
417
+ "default_value": f.default_value,
418
+ }
419
+ for f in self.output_field_config.fields
420
+ ],
421
+ }
422
+ return result
369
423
 
370
424
 
371
425
  class NodeFilter(NodeSingleInput):
@@ -410,7 +464,7 @@ class NodeJoin(NodeMultiInput):
410
464
 
411
465
  def to_yaml_dict(self) -> NodeJoinYaml:
412
466
  """Converts the join node settings to a dictionary for YAML serialization."""
413
- return {
467
+ result: NodeJoinYaml = {
414
468
  "cache_results": self.cache_results,
415
469
  "auto_generate_selection": self.auto_generate_selection,
416
470
  "verify_integrity": self.verify_integrity,
@@ -419,6 +473,21 @@ class NodeJoin(NodeMultiInput):
419
473
  "auto_keep_right": self.auto_keep_right,
420
474
  "auto_keep_left": self.auto_keep_left,
421
475
  }
476
+ if self.output_field_config:
477
+ result["output_field_config"] = {
478
+ "enabled": self.output_field_config.enabled,
479
+ "validation_mode_behavior": self.output_field_config.validation_mode_behavior,
480
+ "validate_data_types": self.output_field_config.validate_data_types,
481
+ "fields": [
482
+ {
483
+ "name": f.name,
484
+ "data_type": f.data_type,
485
+ "default_value": f.default_value,
486
+ }
487
+ for f in self.output_field_config.fields
488
+ ],
489
+ }
490
+ return result
422
491
 
423
492
 
424
493
  class NodeCrossJoin(NodeMultiInput):
@@ -433,7 +502,7 @@ class NodeCrossJoin(NodeMultiInput):
433
502
 
434
503
  def to_yaml_dict(self) -> NodeCrossJoinYaml:
435
504
  """Converts the cross join node settings to a dictionary for YAML serialization."""
436
- return {
505
+ result: NodeCrossJoinYaml = {
437
506
  "cache_results": self.cache_results,
438
507
  "auto_generate_selection": self.auto_generate_selection,
439
508
  "verify_integrity": self.verify_integrity,
@@ -442,6 +511,21 @@ class NodeCrossJoin(NodeMultiInput):
442
511
  "auto_keep_right": self.auto_keep_right,
443
512
  "auto_keep_left": self.auto_keep_left,
444
513
  }
514
+ if self.output_field_config:
515
+ result["output_field_config"] = {
516
+ "enabled": self.output_field_config.enabled,
517
+ "validation_mode_behavior": self.output_field_config.validation_mode_behavior,
518
+ "validate_data_types": self.output_field_config.validate_data_types,
519
+ "fields": [
520
+ {
521
+ "name": f.name,
522
+ "data_type": f.data_type,
523
+ "default_value": f.default_value,
524
+ }
525
+ for f in self.output_field_config.fields
526
+ ],
527
+ }
528
+ return result
445
529
 
446
530
 
447
531
  class NodeFuzzyMatch(NodeJoin):
@@ -451,7 +535,7 @@ class NodeFuzzyMatch(NodeJoin):
451
535
 
452
536
  def to_yaml_dict(self) -> NodeFuzzyMatchYaml:
453
537
  """Converts the fuzzy match node settings to a dictionary for YAML serialization."""
454
- return {
538
+ result: NodeFuzzyMatchYaml = {
455
539
  "cache_results": self.cache_results,
456
540
  "auto_generate_selection": self.auto_generate_selection,
457
541
  "verify_integrity": self.verify_integrity,
@@ -460,6 +544,21 @@ class NodeFuzzyMatch(NodeJoin):
460
544
  "auto_keep_right": self.auto_keep_right,
461
545
  "auto_keep_left": self.auto_keep_left,
462
546
  }
547
+ if self.output_field_config:
548
+ result["output_field_config"] = {
549
+ "enabled": self.output_field_config.enabled,
550
+ "validation_mode_behavior": self.output_field_config.validation_mode_behavior,
551
+ "validate_data_types": self.output_field_config.validate_data_types,
552
+ "fields": [
553
+ {
554
+ "name": f.name,
555
+ "data_type": f.data_type,
556
+ "default_value": f.default_value,
557
+ }
558
+ for f in self.output_field_config.fields
559
+ ],
560
+ }
561
+ return result
463
562
 
464
563
 
465
564
  class NodeDatasource(NodeBase):
@@ -485,6 +584,16 @@ class RawData(BaseModel):
485
584
  columns = [MinimalFieldInfo(name=c, data_type=str(next(data_types))) for c in pylist[0].keys()]
486
585
  return cls(columns=columns, data=values)
487
586
 
587
+ @classmethod
588
+ def from_pydict(cls, pydict: dict[str, list]):
589
+ """Creates a RawData object from a dictionary of lists."""
590
+ if len(pydict) == 0:
591
+ return cls(columns=[], data=[])
592
+ values = [standardize_col_dtype(column_values) for column_values in pydict.values()]
593
+ data_types = (pl.DataType.from_python(type(next((v for v in column_values), None))) for column_values in values)
594
+ columns = [MinimalFieldInfo(name=c, data_type=str(next(data_types))) for c in pydict.keys()]
595
+ return cls(columns=columns, data=values)
596
+
488
597
  def to_pylist(self) -> list[dict]:
489
598
  """Converts the RawData object back into a list of Python dictionaries."""
490
599
  return [{c.name: self.data[ci][ri] for ci, c in enumerate(self.columns)} for ri in range(len(self.data[0]))]
@@ -691,10 +800,25 @@ class NodeOutput(NodeSingleInput):
691
800
 
692
801
  def to_yaml_dict(self) -> NodeOutputYaml:
693
802
  """Converts the output node settings to a dictionary for YAML serialization."""
694
- return {
803
+ result: NodeOutputYaml = {
695
804
  "cache_results": self.cache_results,
696
805
  "output_settings": self.output_settings.to_yaml_dict(),
697
806
  }
807
+ if self.output_field_config:
808
+ result["output_field_config"] = {
809
+ "enabled": self.output_field_config.enabled,
810
+ "validation_mode_behavior": self.output_field_config.validation_mode_behavior,
811
+ "validate_data_types": self.output_field_config.validate_data_types,
812
+ "fields": [
813
+ {
814
+ "name": f.name,
815
+ "data_type": f.data_type,
816
+ "default_value": f.default_value,
817
+ }
818
+ for f in self.output_field_config.fields
819
+ ],
820
+ }
821
+ return result
698
822
 
699
823
 
700
824
  class NodeOutputConnection(BaseModel):
@@ -106,6 +106,8 @@ class FlowGraphConfig(BaseModel):
106
106
  path (str): The file path associated with the flow.
107
107
  execution_mode (ExecutionModeLiteral): The mode of execution ('Development' or 'Performance').
108
108
  execution_location (ExecutionLocationsLiteral): The location for execution ('local', 'remote').
109
+ max_parallel_workers (int): Maximum number of threads used for parallel node execution within a
110
+ stage. Set to 1 to disable parallelism. Defaults to 4.
109
111
  """
110
112
 
111
113
  flow_id: int = Field(default_factory=create_unique_id, description="Unique identifier for the flow.")
@@ -115,6 +117,7 @@ class FlowGraphConfig(BaseModel):
115
117
  path: str = ""
116
118
  execution_mode: ExecutionModeLiteral = "Performance"
117
119
  execution_location: ExecutionLocationsLiteral = Field(default_factory=get_global_execution_location)
120
+ max_parallel_workers: int = Field(default=4, ge=1, description="Max threads for parallel node execution.")
118
121
 
119
122
  @field_validator("execution_location", mode="before")
120
123
  def validate_and_set_execution_location(cls, v: ExecutionLocationsLiteral | None) -> ExecutionLocationsLiteral:
@@ -143,6 +146,7 @@ class FlowSettings(FlowGraphConfig):
143
146
  show_detailed_progress (bool): Flag to show detailed progress during execution.
144
147
  is_running (bool): Indicates if the flow is currently running.
145
148
  is_canceled (bool): Indicates if the flow execution has been canceled.
149
+ track_history (bool): Flag to enable or disable undo/redo history tracking.
146
150
  """
147
151
 
148
152
  auto_save: bool = False
@@ -150,6 +154,7 @@ class FlowSettings(FlowGraphConfig):
150
154
  show_detailed_progress: bool = True
151
155
  is_running: bool = False
152
156
  is_canceled: bool = False
157
+ track_history: bool = True
153
158
 
154
159
  @classmethod
155
160
  def from_flow_settings_input(cls, flow_graph_config: FlowGraphConfig):
@@ -190,6 +195,7 @@ class FlowfileSettings(BaseModel):
190
195
  execution_location: ExecutionLocationsLiteral = "local"
191
196
  auto_save: bool = False
192
197
  show_detailed_progress: bool = True
198
+ max_parallel_workers: int = Field(default=4, ge=1)
193
199
 
194
200
 
195
201
  class FlowfileNode(BaseModel):
@@ -199,6 +205,7 @@ class FlowfileNode(BaseModel):
199
205
  type: str
200
206
  is_start_node: bool = False
201
207
  description: str | None = ""
208
+ node_reference: str | None = None # Unique reference identifier for code generation
202
209
  x_position: int | None = 0
203
210
  y_position: int | None = 0
204
211
  left_input_id: int | None = None
@@ -214,6 +221,7 @@ class FlowfileNode(BaseModel):
214
221
  "pos_y",
215
222
  "is_setup",
216
223
  "description",
224
+ "node_reference",
217
225
  "user_id",
218
226
  "is_flow_output",
219
227
  "is_user_defined",
@@ -286,6 +294,7 @@ class NodeInformation(BaseModel):
286
294
  is_setup: bool | None = None
287
295
  is_start_node: bool = False
288
296
  description: str | None = ""
297
+ node_reference: str | None = None # Unique reference identifier for code generation
289
298
  x_position: int | None = 0
290
299
  y_position: int | None = 0
291
300
  left_input_id: int | None = None