@nocobase/flow-engine 2.0.0-beta.9 → 2.0.1

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 (245) hide show
  1. package/lib/BlockScopedFlowEngine.js +0 -1
  2. package/lib/FlowDefinition.d.ts +2 -0
  3. package/lib/JSRunner.d.ts +6 -0
  4. package/lib/JSRunner.js +32 -2
  5. package/lib/ViewScopedFlowEngine.js +3 -0
  6. package/lib/acl/Acl.js +13 -3
  7. package/lib/components/FlowContextSelector.js +155 -10
  8. package/lib/components/settings/wrappers/component/SwitchWithTitle.js +2 -1
  9. package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.js +76 -15
  10. package/lib/components/settings/wrappers/contextual/FlowsContextMenu.js +24 -4
  11. package/lib/components/settings/wrappers/contextual/StepSettingsDialog.js +5 -1
  12. package/lib/components/variables/VariableInput.js +9 -4
  13. package/lib/components/variables/VariableTag.js +46 -39
  14. package/lib/components/variables/utils.d.ts +7 -0
  15. package/lib/components/variables/utils.js +42 -2
  16. package/lib/data-source/index.d.ts +7 -27
  17. package/lib/data-source/index.js +81 -51
  18. package/lib/executor/FlowExecutor.d.ts +2 -1
  19. package/lib/executor/FlowExecutor.js +163 -22
  20. package/lib/flowContext.d.ts +230 -7
  21. package/lib/flowContext.js +2267 -148
  22. package/lib/flowEngine.d.ts +21 -0
  23. package/lib/flowEngine.js +56 -8
  24. package/lib/flowI18n.js +6 -4
  25. package/lib/flowSettings.js +17 -11
  26. package/lib/index.d.ts +7 -1
  27. package/lib/index.js +21 -0
  28. package/lib/locale/en-US.json +9 -2
  29. package/lib/locale/index.d.ts +14 -0
  30. package/lib/locale/zh-CN.json +8 -1
  31. package/lib/models/CollectionFieldModel.d.ts +1 -0
  32. package/lib/models/CollectionFieldModel.js +3 -2
  33. package/lib/models/flowModel.js +12 -1
  34. package/lib/provider.js +5 -5
  35. package/lib/resources/baseRecordResource.d.ts +5 -0
  36. package/lib/resources/baseRecordResource.js +24 -0
  37. package/lib/resources/multiRecordResource.d.ts +1 -0
  38. package/lib/resources/multiRecordResource.js +11 -4
  39. package/lib/resources/singleRecordResource.js +2 -0
  40. package/lib/resources/sqlResource.d.ts +4 -3
  41. package/lib/resources/sqlResource.js +8 -3
  42. package/lib/runjs-context/contexts/FormJSFieldItemRunJSContext.js +12 -2
  43. package/lib/runjs-context/contexts/JSBlockRunJSContext.js +2 -2
  44. package/lib/runjs-context/contexts/JSEditableFieldRunJSContext.d.ts +16 -0
  45. package/lib/runjs-context/contexts/JSEditableFieldRunJSContext.js +125 -0
  46. package/lib/runjs-context/contexts/JSItemRunJSContext.js +12 -2
  47. package/lib/runjs-context/contexts/base.js +706 -41
  48. package/lib/runjs-context/contributions.d.ts +33 -0
  49. package/lib/runjs-context/contributions.js +88 -0
  50. package/lib/runjs-context/helpers.js +12 -1
  51. package/lib/runjs-context/setup.js +6 -0
  52. package/lib/runjs-context/snippets/global/api-request.snippet.js +3 -3
  53. package/lib/runjs-context/snippets/global/import-esm.snippet.js +2 -3
  54. package/lib/runjs-context/snippets/global/query-selector.snippet.js +8 -3
  55. package/lib/runjs-context/snippets/global/require-amd.snippet.js +1 -1
  56. package/lib/runjs-context/snippets/index.d.ts +11 -1
  57. package/lib/runjs-context/snippets/index.js +61 -40
  58. package/lib/runjs-context/snippets/scene/block/add-event-listener.snippet.js +10 -7
  59. package/lib/runjs-context/snippets/scene/block/api-fetch-render-list.snippet.js +3 -3
  60. package/lib/runjs-context/snippets/scene/block/chartjs-bar.snippet.js +2 -2
  61. package/lib/runjs-context/snippets/scene/block/echarts-init.snippet.js +2 -2
  62. package/lib/runjs-context/snippets/scene/block/render-iframe.snippet.js +2 -2
  63. package/lib/runjs-context/snippets/scene/block/render-react.snippet.js +1 -1
  64. package/lib/runjs-context/snippets/scene/block/render-statistics.snippet.js +1 -1
  65. package/lib/runjs-context/snippets/scene/block/render-timeline.snippet.js +1 -1
  66. package/lib/runjs-context/snippets/scene/block/resource-example.snippet.js +5 -5
  67. package/lib/runjs-context/snippets/scene/block/three-users-orbit.snippet.js +6 -6
  68. package/lib/runjs-context/snippets/scene/block/vue-component.snippet.js +3 -4
  69. package/lib/runjs-context/snippets/scene/detail/color-by-value.snippet.js +1 -1
  70. package/lib/runjs-context/snippets/scene/detail/copy-to-clipboard.snippet.js +20 -3
  71. package/lib/runjs-context/snippets/scene/detail/format-number.snippet.js +1 -1
  72. package/lib/runjs-context/snippets/scene/detail/innerHTML-value.snippet.js +1 -1
  73. package/lib/runjs-context/snippets/scene/detail/percentage-bar.snippet.js +3 -3
  74. package/lib/runjs-context/snippets/scene/detail/relative-time.snippet.js +3 -3
  75. package/lib/runjs-context/snippets/scene/detail/status-tag.snippet.js +2 -2
  76. package/lib/runjs-context/snippets/scene/form/cascade-select.snippet.js +1 -1
  77. package/lib/runjs-context/snippets/scene/form/render-basic.snippet.js +2 -2
  78. package/lib/runjs-context/snippets/scene/table/cell-open-dialog.snippet.js +6 -3
  79. package/lib/runjs-context/snippets/scene/table/concat-fields.snippet.js +3 -1
  80. package/lib/runjsLibs.d.ts +28 -0
  81. package/lib/runjsLibs.js +532 -0
  82. package/lib/scheduler/ModelOperationScheduler.d.ts +2 -0
  83. package/lib/scheduler/ModelOperationScheduler.js +25 -21
  84. package/lib/types.d.ts +27 -0
  85. package/lib/utils/associationObjectVariable.d.ts +2 -2
  86. package/lib/utils/createCollectionContextMeta.js +1 -0
  87. package/lib/utils/createEphemeralContext.js +2 -2
  88. package/lib/utils/dateVariable.d.ts +16 -0
  89. package/lib/utils/dateVariable.js +380 -0
  90. package/lib/utils/exceptions.d.ts +7 -0
  91. package/lib/utils/exceptions.js +10 -0
  92. package/lib/utils/index.d.ts +8 -3
  93. package/lib/utils/index.js +45 -0
  94. package/lib/utils/params-resolvers.js +16 -9
  95. package/lib/utils/resolveModuleUrl.d.ts +58 -0
  96. package/lib/utils/resolveModuleUrl.js +65 -0
  97. package/lib/utils/resolveRunJSObjectValues.d.ts +16 -0
  98. package/lib/utils/resolveRunJSObjectValues.js +61 -0
  99. package/lib/utils/runjsModuleLoader.d.ts +58 -0
  100. package/lib/utils/runjsModuleLoader.js +422 -0
  101. package/lib/utils/runjsTemplateCompat.d.ts +35 -0
  102. package/lib/utils/runjsTemplateCompat.js +743 -0
  103. package/lib/utils/runjsValue.d.ts +29 -0
  104. package/lib/utils/runjsValue.js +275 -0
  105. package/lib/utils/safeGlobals.d.ts +18 -8
  106. package/lib/utils/safeGlobals.js +164 -17
  107. package/lib/utils/schema-utils.d.ts +10 -0
  108. package/lib/utils/schema-utils.js +61 -0
  109. package/lib/views/createViewMeta.d.ts +0 -7
  110. package/lib/views/createViewMeta.js +19 -70
  111. package/lib/views/index.d.ts +1 -2
  112. package/lib/views/index.js +4 -3
  113. package/lib/views/useDialog.js +7 -2
  114. package/lib/views/useDrawer.js +7 -2
  115. package/lib/views/usePage.d.ts +4 -0
  116. package/lib/views/usePage.js +43 -6
  117. package/lib/views/usePopover.js +4 -1
  118. package/lib/views/viewEvents.d.ts +17 -0
  119. package/lib/views/viewEvents.js +90 -0
  120. package/package.json +4 -4
  121. package/src/BlockScopedFlowEngine.ts +2 -5
  122. package/src/JSRunner.ts +44 -2
  123. package/src/ViewScopedFlowEngine.ts +4 -0
  124. package/src/__tests__/JSRunner.test.ts +64 -0
  125. package/src/__tests__/createViewMeta.popup.test.ts +62 -1
  126. package/src/__tests__/flowContext.test.ts +693 -1
  127. package/src/__tests__/flowEngine.dataSourceDirty.test.ts +63 -0
  128. package/src/__tests__/flowModel.openView.navigation.test.ts +28 -0
  129. package/src/__tests__/flowRunJSContextDefine.test.ts +63 -0
  130. package/src/__tests__/flowRuntimeContext.test.ts +2 -1
  131. package/src/__tests__/flowSettings.open.test.tsx +123 -19
  132. package/src/__tests__/runjsContext.test.ts +10 -7
  133. package/src/__tests__/runjsContextImplementations.test.ts +34 -3
  134. package/src/__tests__/runjsContextRuntime.test.ts +3 -3
  135. package/src/__tests__/runjsContributions.test.ts +89 -0
  136. package/src/__tests__/runjsExternalLibs.test.ts +242 -0
  137. package/src/__tests__/runjsLibsLazyLoading.test.ts +44 -0
  138. package/src/__tests__/runjsLocales.test.ts +4 -1
  139. package/src/__tests__/runjsPreprocessDefault.test.ts +49 -0
  140. package/src/__tests__/runjsRuntimeFeatures.test.ts +166 -0
  141. package/src/__tests__/runjsSnippets.test.ts +40 -3
  142. package/src/acl/Acl.tsx +3 -3
  143. package/src/components/FlowContextSelector.tsx +208 -12
  144. package/src/components/settings/wrappers/component/SwitchWithTitle.tsx +2 -1
  145. package/src/components/settings/wrappers/component/__tests__/InlineControls.test.tsx +74 -0
  146. package/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +109 -16
  147. package/src/components/settings/wrappers/contextual/FlowsContextMenu.tsx +41 -7
  148. package/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx +13 -2
  149. package/src/components/settings/wrappers/contextual/__tests__/DefaultSettingsIcon.test.tsx +157 -5
  150. package/src/components/variables/VariableInput.tsx +12 -4
  151. package/src/components/variables/VariableTag.tsx +54 -45
  152. package/src/components/variables/__tests__/FlowContextSelector.test.tsx +260 -3
  153. package/src/components/variables/__tests__/VariableTag.test.tsx +50 -0
  154. package/src/components/variables/__tests__/utils.test.ts +81 -3
  155. package/src/components/variables/utils.ts +67 -6
  156. package/src/data-source/index.ts +85 -110
  157. package/src/executor/FlowExecutor.ts +200 -23
  158. package/src/executor/__tests__/flowExecutor.test.ts +66 -0
  159. package/src/flowContext.ts +2986 -211
  160. package/src/flowEngine.ts +59 -8
  161. package/src/flowI18n.ts +7 -5
  162. package/src/flowSettings.ts +18 -12
  163. package/src/index.ts +14 -1
  164. package/src/locale/en-US.json +9 -2
  165. package/src/locale/zh-CN.json +8 -1
  166. package/src/models/CollectionFieldModel.tsx +3 -1
  167. package/src/models/__tests__/dispatchEvent.when.test.ts +554 -0
  168. package/src/models/__tests__/flowModel.test.ts +20 -4
  169. package/src/models/flowModel.tsx +13 -1
  170. package/src/provider.tsx +7 -6
  171. package/src/resources/__tests__/multiRecordResource.test.ts +44 -0
  172. package/src/resources/__tests__/sqlResource.test.ts +60 -0
  173. package/src/resources/baseRecordResource.ts +31 -0
  174. package/src/resources/multiRecordResource.ts +11 -4
  175. package/src/resources/singleRecordResource.ts +3 -0
  176. package/src/resources/sqlResource.ts +11 -6
  177. package/src/runjs-context/contexts/FormJSFieldItemRunJSContext.ts +10 -0
  178. package/src/runjs-context/contexts/JSBlockRunJSContext.ts +6 -2
  179. package/src/runjs-context/contexts/JSEditableFieldRunJSContext.ts +106 -0
  180. package/src/runjs-context/contexts/JSItemRunJSContext.ts +10 -0
  181. package/src/runjs-context/contexts/base.ts +715 -44
  182. package/src/runjs-context/contributions.ts +88 -0
  183. package/src/runjs-context/helpers.ts +11 -1
  184. package/src/runjs-context/setup.ts +6 -0
  185. package/src/runjs-context/snippets/global/api-request.snippet.ts +3 -3
  186. package/src/runjs-context/snippets/global/import-esm.snippet.ts +2 -3
  187. package/src/runjs-context/snippets/global/query-selector.snippet.ts +8 -3
  188. package/src/runjs-context/snippets/global/require-amd.snippet.ts +1 -1
  189. package/src/runjs-context/snippets/index.ts +75 -41
  190. package/src/runjs-context/snippets/scene/block/add-event-listener.snippet.ts +11 -13
  191. package/src/runjs-context/snippets/scene/block/api-fetch-render-list.snippet.ts +3 -3
  192. package/src/runjs-context/snippets/scene/block/chartjs-bar.snippet.ts +2 -2
  193. package/src/runjs-context/snippets/scene/block/echarts-init.snippet.ts +2 -2
  194. package/src/runjs-context/snippets/scene/block/render-iframe.snippet.ts +2 -2
  195. package/src/runjs-context/snippets/scene/block/render-react.snippet.ts +1 -1
  196. package/src/runjs-context/snippets/scene/block/render-statistics.snippet.ts +1 -1
  197. package/src/runjs-context/snippets/scene/block/render-timeline.snippet.ts +1 -1
  198. package/src/runjs-context/snippets/scene/block/resource-example.snippet.ts +6 -11
  199. package/src/runjs-context/snippets/scene/block/three-users-orbit.snippet.ts +6 -6
  200. package/src/runjs-context/snippets/scene/block/vue-component.snippet.ts +3 -4
  201. package/src/runjs-context/snippets/scene/detail/color-by-value.snippet.ts +1 -1
  202. package/src/runjs-context/snippets/scene/detail/copy-to-clipboard.snippet.ts +20 -3
  203. package/src/runjs-context/snippets/scene/detail/format-number.snippet.ts +1 -1
  204. package/src/runjs-context/snippets/scene/detail/innerHTML-value.snippet.ts +1 -1
  205. package/src/runjs-context/snippets/scene/detail/percentage-bar.snippet.ts +3 -3
  206. package/src/runjs-context/snippets/scene/detail/relative-time.snippet.ts +3 -3
  207. package/src/runjs-context/snippets/scene/detail/status-tag.snippet.ts +2 -2
  208. package/src/runjs-context/snippets/scene/form/cascade-select.snippet.ts +1 -1
  209. package/src/runjs-context/snippets/scene/form/render-basic.snippet.ts +3 -8
  210. package/src/runjs-context/snippets/scene/table/cell-open-dialog.snippet.ts +6 -3
  211. package/src/runjs-context/snippets/scene/table/concat-fields.snippet.ts +3 -1
  212. package/src/runjsLibs.ts +622 -0
  213. package/src/scheduler/ModelOperationScheduler.ts +27 -21
  214. package/src/types.ts +38 -1
  215. package/src/utils/__tests__/dateVariable.test.ts +101 -0
  216. package/src/utils/__tests__/params-resolvers.test.ts +40 -0
  217. package/src/utils/__tests__/runjsRequireAsyncAutoWhitelist.test.ts +38 -0
  218. package/src/utils/__tests__/runjsTemplateCompat.test.ts +159 -0
  219. package/src/utils/__tests__/runjsValue.test.ts +44 -0
  220. package/src/utils/__tests__/safeGlobals.test.ts +57 -2
  221. package/src/utils/__tests__/utils.test.ts +95 -0
  222. package/src/utils/associationObjectVariable.ts +2 -2
  223. package/src/utils/createCollectionContextMeta.ts +1 -0
  224. package/src/utils/createEphemeralContext.ts +5 -4
  225. package/src/utils/dateVariable.ts +397 -0
  226. package/src/utils/exceptions.ts +11 -0
  227. package/src/utils/index.ts +37 -3
  228. package/src/utils/params-resolvers.ts +23 -9
  229. package/src/utils/resolveModuleUrl.ts +91 -0
  230. package/src/utils/resolveRunJSObjectValues.ts +46 -0
  231. package/src/utils/runjsModuleLoader.ts +553 -0
  232. package/src/utils/runjsTemplateCompat.ts +828 -0
  233. package/src/utils/runjsValue.ts +287 -0
  234. package/src/utils/safeGlobals.ts +188 -17
  235. package/src/utils/schema-utils.ts +79 -0
  236. package/src/views/__tests__/FlowView.usePage.test.tsx +54 -1
  237. package/src/views/__tests__/useDialog.closeDestroy.test.tsx +35 -8
  238. package/src/views/__tests__/viewEvents.resolveOpenerEngine.test.ts +28 -0
  239. package/src/views/createViewMeta.ts +22 -75
  240. package/src/views/index.tsx +1 -2
  241. package/src/views/useDialog.tsx +8 -1
  242. package/src/views/useDrawer.tsx +8 -1
  243. package/src/views/usePage.tsx +51 -5
  244. package/src/views/usePopover.tsx +4 -1
  245. package/src/views/viewEvents.ts +55 -0
@@ -13,6 +13,7 @@ import {
13
13
  isInheritedFrom,
14
14
  resolveDefaultParams,
15
15
  resolveStepUiSchema,
16
+ resolveStepDisabledInSettings,
16
17
  shouldHideStepInSettings,
17
18
  FlowExitException,
18
19
  defineAction,
@@ -1117,4 +1118,98 @@ describe('Utils', () => {
1117
1118
  consoleSpy.mockRestore();
1118
1119
  });
1119
1120
  });
1121
+
1122
+ // ==================== resolveStepDisabledInSettings() FUNCTION ====================
1123
+ describe('resolveStepDisabledInSettings()', () => {
1124
+ let mockFlow: any;
1125
+ let mockStep: StepDefinition;
1126
+
1127
+ beforeEach(() => {
1128
+ mockFlow = {
1129
+ key: 'testFlow',
1130
+ title: 'Test Flow',
1131
+ steps: {},
1132
+ };
1133
+
1134
+ mockStep = {
1135
+ key: 'testStep',
1136
+ handler: vi.fn(),
1137
+ };
1138
+ });
1139
+
1140
+ test('returns disabled=false when step is falsy', async () => {
1141
+ const result = await resolveStepDisabledInSettings(mockModel, mockFlow, null as any);
1142
+ expect(result).toEqual({ disabled: false });
1143
+ });
1144
+
1145
+ test('respects static step.disabledInSettings and disabledReasonInSettings', async () => {
1146
+ mockStep.disabledInSettings = true;
1147
+ mockStep.disabledReasonInSettings = 'legacy reason';
1148
+
1149
+ const result = await resolveStepDisabledInSettings(mockModel, mockFlow, mockStep);
1150
+ expect(result).toEqual({ disabled: true, reason: 'legacy reason' });
1151
+ });
1152
+
1153
+ test('falls back to action disabled settings when step value is undefined', async () => {
1154
+ const action: ActionDefinition = {
1155
+ name: 'testAction',
1156
+ handler: vi.fn(),
1157
+ disabledInSettings: true,
1158
+ disabledReasonInSettings: 'from action',
1159
+ } as any;
1160
+
1161
+ mockStep.use = 'testAction';
1162
+ mockModel.flowEngine.getAction = vi.fn().mockReturnValue(action);
1163
+
1164
+ const result = await resolveStepDisabledInSettings(mockModel, mockFlow, mockStep);
1165
+ expect(result).toEqual({ disabled: true, reason: 'from action' });
1166
+ });
1167
+
1168
+ test('prefers step disabled settings over action values', async () => {
1169
+ const action: ActionDefinition = {
1170
+ name: 'testAction',
1171
+ handler: vi.fn(),
1172
+ disabledInSettings: false,
1173
+ disabledReasonInSettings: 'from action',
1174
+ } as any;
1175
+
1176
+ mockStep.use = 'testAction';
1177
+ mockStep.disabledInSettings = true;
1178
+ mockStep.disabledReasonInSettings = 'from step';
1179
+ mockModel.flowEngine.getAction = vi.fn().mockReturnValue(action);
1180
+
1181
+ const result = await resolveStepDisabledInSettings(mockModel, mockFlow, mockStep);
1182
+ expect(result).toEqual({ disabled: true, reason: 'from step' });
1183
+ });
1184
+
1185
+ test('evaluates function disabled settings with FlowRuntimeContext', async () => {
1186
+ const disabledFn = vi.fn().mockResolvedValue(true);
1187
+ const reasonFn = vi.fn().mockResolvedValue('computed reason');
1188
+ mockStep.disabledInSettings = disabledFn as any;
1189
+ mockStep.disabledReasonInSettings = reasonFn as any;
1190
+
1191
+ const result = await resolveStepDisabledInSettings(mockModel, mockFlow, mockStep);
1192
+
1193
+ expect(disabledFn).toHaveBeenCalledTimes(1);
1194
+ expect(reasonFn).toHaveBeenCalledTimes(1);
1195
+ const disabledCtx = disabledFn.mock.calls[0][0] as FlowRuntimeContext;
1196
+ const reasonCtx = reasonFn.mock.calls[0][0] as FlowRuntimeContext;
1197
+ expect(disabledCtx).toBeInstanceOf(FlowRuntimeContext);
1198
+ expect(reasonCtx).toBeInstanceOf(FlowRuntimeContext);
1199
+ expect((disabledCtx as any).currentStep).toBeInstanceOf(ContextPathProxy);
1200
+ expect(String((disabledCtx as any).currentStep)).toBe('{{ctx.currentStep}}');
1201
+ expect(result).toEqual({ disabled: true, reason: 'computed reason' });
1202
+ });
1203
+
1204
+ test('returns disabled=false when function disabledInSettings throws', async () => {
1205
+ const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
1206
+ mockStep.disabledInSettings = vi.fn().mockRejectedValue(new Error('boom')) as any;
1207
+
1208
+ const result = await resolveStepDisabledInSettings(mockModel, mockFlow, mockStep);
1209
+
1210
+ expect(consoleSpy).toHaveBeenCalled();
1211
+ expect(result).toEqual({ disabled: false });
1212
+ consoleSpy.mockRestore();
1213
+ });
1214
+ });
1120
1215
  });
@@ -71,7 +71,7 @@ function toFilterByTk(value: unknown, primaryKey: string | string[]) {
71
71
  }
72
72
 
73
73
  /**
74
- * 创建一个用于“对象类变量”(如 formValues / currentObject)的 `resolveOnServer` 判定函数。
74
+ * 创建一个用于“对象类变量”(如 formValues / item)的 `resolveOnServer` 判定函数。
75
75
  * 仅当访问路径以“关联字段名”开头(且继续访问其子属性)时,返回 true 交由服务端解析;
76
76
  * 否则在前端解析即可。
77
77
  *
@@ -114,7 +114,7 @@ export function createAssociationSubpathResolver(
114
114
  *
115
115
  * @param collectionAccessor 获取集合对象,用于字段/元信息来源
116
116
  * @param title 变量组标题(用于 UI 展示)
117
- * @param valueAccessor 获取当前对象值(如 ctx.form.getFieldsValue() / ctx.currentObject
117
+ * @param valueAccessor 获取当前对象值(如 ctx.form.getFieldsValue() / ctx.item
118
118
  * @returns PropertyMetaFactory
119
119
  */
120
120
  export function createAssociationAwareObjectMetaFactory(
@@ -88,6 +88,7 @@ function createMetaBaseProperties(field: CollectionField) {
88
88
  return {
89
89
  title: field.title || field.name,
90
90
  interface: field.interface,
91
+ options: field.options,
91
92
  uiSchema: field.uiSchema || {},
92
93
  };
93
94
  }
@@ -7,9 +7,8 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
 
10
- import { FlowContext } from '../flowContext';
10
+ import { FlowContext, type FlowContextMethodInfoInput, type PropertyOptions } from '../flowContext';
11
11
  import type { ActionDefinition } from '../types';
12
- import type { PropertyOptions } from '../flowContext';
13
12
 
14
13
  // 临时上下文:
15
14
  // - 读:优先从 scoped(临时定义)读取,兜底 parent;
@@ -51,7 +50,8 @@ export async function createEphemeralContext<TCtx extends FlowContext>(
51
50
  return (propKey: string, options: any) => (scoped as FlowContext).defineProperty(propKey, options);
52
51
  }
53
52
  if (key === 'defineMethod') {
54
- return (name: string, fn: any, des?: string) => (scoped as FlowContext).defineMethod(name, fn, des);
53
+ return (name: string, fn: any, info?: string | FlowContextMethodInfoInput) =>
54
+ (scoped as FlowContext).defineMethod(name, fn, info);
55
55
  }
56
56
  if (Reflect.has(scopedObj, key)) {
57
57
  return Reflect.get(scopedObj, key, receiver);
@@ -118,7 +118,8 @@ export async function createEphemeralContext<TCtx extends FlowContext>(
118
118
  return (propKey: string, options: any) => (parent as FlowContext).defineProperty(propKey, options);
119
119
  }
120
120
  if (key === 'defineMethod') {
121
- return (name: string, fn: any, des?: string) => (parent as FlowContext).defineMethod(name, fn, des);
121
+ return (name: string, fn: any, info?: string | FlowContextMethodInfoInput) =>
122
+ (parent as FlowContext).defineMethod(name, fn, info);
122
123
  }
123
124
  if (Reflect.has(scopedObj, key)) {
124
125
  return Reflect.get(scopedObj, key, receiver);
@@ -0,0 +1,397 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ import dayjs from 'dayjs';
11
+
12
+ const CTX_DATE_REGEX = /^\{\{\s*ctx\.date(?:\.(.+?))?\s*\}\}$/;
13
+
14
+ const PRESET_KEYS = new Set([
15
+ 'today',
16
+ 'now',
17
+ 'yesterday',
18
+ 'tomorrow',
19
+ 'thisWeek',
20
+ 'lastWeek',
21
+ 'nextWeek',
22
+ 'thisMonth',
23
+ 'lastMonth',
24
+ 'nextMonth',
25
+ 'thisQuarter',
26
+ 'lastQuarter',
27
+ 'nextQuarter',
28
+ 'thisYear',
29
+ 'lastYear',
30
+ 'nextYear',
31
+ ]);
32
+
33
+ const RELATIVE_DIRECTIONS = new Set(['next', 'past']);
34
+ const RELATIVE_UNITS = new Set(['day', 'week', 'month', 'year']);
35
+
36
+ function parseCtxDateSegments(value: string): string[] | null {
37
+ if (typeof value !== 'string') return null;
38
+ const trimmed = value.trim();
39
+ const match = trimmed.match(CTX_DATE_REGEX);
40
+ if (!match) return null;
41
+ const rawPath = String(match[1] || '');
42
+ if (!rawPath) return [];
43
+ return rawPath
44
+ .split('.')
45
+ .map((seg) => seg.trim())
46
+ .filter(Boolean);
47
+ }
48
+
49
+ export function isCtxDatePathPrefix(pathSegments: string[]): boolean {
50
+ const segments = withDatePrefix((pathSegments || []).map((seg) => String(seg)));
51
+ if (segments[0] !== 'date') return false;
52
+ if (segments.length === 1) return true;
53
+
54
+ if (segments[1] === 'preset') {
55
+ if (segments.length === 2) return true;
56
+ return segments.length === 3 && PRESET_KEYS.has(segments[2]);
57
+ }
58
+
59
+ if (segments[1] === 'relative') {
60
+ if (segments.length === 2) return true;
61
+ if (segments.length === 3) return RELATIVE_DIRECTIONS.has(segments[2]);
62
+ if (segments.length === 4) {
63
+ return RELATIVE_DIRECTIONS.has(segments[2]) && RELATIVE_UNITS.has(segments[3]);
64
+ }
65
+ if (segments.length === 5) {
66
+ return (
67
+ RELATIVE_DIRECTIONS.has(segments[2]) &&
68
+ RELATIVE_UNITS.has(segments[3]) &&
69
+ typeof parseNumberToken(segments[4]) === 'number'
70
+ );
71
+ }
72
+ return false;
73
+ }
74
+
75
+ if (segments[1] === 'exact') {
76
+ if (segments.length === 2) return true;
77
+
78
+ if (segments[2] === 'single') {
79
+ if (segments.length === 3) return true;
80
+ if (segments.length === 4) return segments[3] === 'date';
81
+ if (segments.length === 5) return segments[3] === 'date' && /^v.+/.test(segments[4]);
82
+ return false;
83
+ }
84
+
85
+ if (segments[2] === 'range') {
86
+ if (segments.length === 3) return true;
87
+ if (segments.length === 4) return segments[3] === 'date';
88
+ if (segments.length === 5) return segments[3] === 'date' && /^v.+/.test(segments[4]);
89
+ if (segments.length === 6) return segments[3] === 'date' && /^v.+/.test(segments[4]) && /^v.+/.test(segments[5]);
90
+ return false;
91
+ }
92
+
93
+ return false;
94
+ }
95
+
96
+ return false;
97
+ }
98
+
99
+ function withDatePrefix(pathSegments: string[]): string[] {
100
+ if (pathSegments[0] === 'date') {
101
+ return pathSegments;
102
+ }
103
+ return ['date', ...pathSegments];
104
+ }
105
+
106
+ function toCtxDateExpression(pathSegments: string[]): string {
107
+ const segs = withDatePrefix(pathSegments);
108
+ return `{{ ctx.${segs.join('.')} }}`;
109
+ }
110
+
111
+ function utf8ToBase64(input: string): string {
112
+ const globalBuffer = (globalThis as any)?.Buffer;
113
+ if (globalBuffer && typeof globalBuffer.from === 'function') {
114
+ return globalBuffer.from(input, 'utf8').toString('base64');
115
+ }
116
+
117
+ if (typeof btoa === 'function') {
118
+ const encoded = encodeURIComponent(input).replace(/%([0-9A-F]{2})/g, (_m, p1) =>
119
+ String.fromCharCode(parseInt(p1, 16)),
120
+ );
121
+ return btoa(encoded);
122
+ }
123
+
124
+ throw new Error('No base64 encoder available');
125
+ }
126
+
127
+ function base64ToUtf8(input: string): string {
128
+ const globalBuffer = (globalThis as any)?.Buffer;
129
+ if (globalBuffer && typeof globalBuffer.from === 'function') {
130
+ return globalBuffer.from(input, 'base64').toString('utf8');
131
+ }
132
+
133
+ if (typeof atob === 'function') {
134
+ const binary = atob(input);
135
+ const encoded = Array.from(binary)
136
+ .map((char) => `%${char.charCodeAt(0).toString(16).padStart(2, '0')}`)
137
+ .join('');
138
+ return decodeURIComponent(encoded);
139
+ }
140
+
141
+ throw new Error('No base64 decoder available');
142
+ }
143
+
144
+ function normalizeToString(value: any): string | undefined {
145
+ if (value == null) return undefined;
146
+ if (typeof value === 'string') {
147
+ const trimmed = value.trim();
148
+ return trimmed.length ? trimmed : undefined;
149
+ }
150
+ if (dayjs.isDayjs(value)) {
151
+ return value.toISOString();
152
+ }
153
+ if (value instanceof Date) {
154
+ return dayjs(value).toISOString();
155
+ }
156
+ if (typeof value === 'number' || typeof value === 'boolean') {
157
+ return String(value);
158
+ }
159
+ return undefined;
160
+ }
161
+
162
+ function parseNumberToken(token: string): number | undefined {
163
+ const match = String(token || '').match(/^n(\d+)$/);
164
+ if (!match) return undefined;
165
+ const parsed = Number(match[1]);
166
+ if (!Number.isFinite(parsed) || parsed <= 0) return undefined;
167
+ return parsed;
168
+ }
169
+
170
+ function startOfIsoWeek(date: dayjs.Dayjs): dayjs.Dayjs {
171
+ const day = date.day();
172
+ const offset = day === 0 ? -6 : 1 - day;
173
+ return date.add(offset, 'day').startOf('day');
174
+ }
175
+
176
+ function startOfQuarter(date: dayjs.Dayjs): dayjs.Dayjs {
177
+ const month = date.month();
178
+ const quarterStartMonth = Math.floor(month / 3) * 3;
179
+ return date.month(quarterStartMonth).startOf('month');
180
+ }
181
+
182
+ export function encodeBase64Url(input: string): string {
183
+ const base64 = utf8ToBase64(String(input || ''));
184
+ return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
185
+ }
186
+
187
+ export function decodeBase64Url(input: string): string | undefined {
188
+ try {
189
+ const raw = String(input || '').replace(/=+$/g, '');
190
+ if (!raw) return undefined;
191
+ if (!/^[A-Za-z0-9_-]+$/.test(raw)) return undefined;
192
+
193
+ const normalized = raw.replace(/-/g, '+').replace(/_/g, '/');
194
+ const padLength = normalized.length % 4 === 0 ? 0 : 4 - (normalized.length % 4);
195
+ const padded = `${normalized}${'='.repeat(padLength)}`;
196
+ const decoded = base64ToUtf8(padded);
197
+
198
+ if (encodeBase64Url(decoded) !== raw) return undefined;
199
+ return decoded;
200
+ } catch (_error) {
201
+ return undefined;
202
+ }
203
+ }
204
+
205
+ export function isCtxDateExpression(value: unknown): value is string {
206
+ if (typeof value !== 'string') return false;
207
+ return CTX_DATE_REGEX.test(value.trim());
208
+ }
209
+
210
+ export function isCompleteCtxDatePath(pathSegments: string[]): boolean {
211
+ if (!isCtxDatePathPrefix(pathSegments)) return false;
212
+ const segments = withDatePrefix((pathSegments || []).map((seg) => String(seg)));
213
+ if (segments[0] !== 'date') return false;
214
+
215
+ if (segments[1] === 'preset') {
216
+ return segments.length === 3 && PRESET_KEYS.has(segments[2]);
217
+ }
218
+
219
+ if (segments[1] === 'relative') {
220
+ if (segments.length !== 5) return false;
221
+ return (
222
+ RELATIVE_DIRECTIONS.has(segments[2]) &&
223
+ RELATIVE_UNITS.has(segments[3]) &&
224
+ typeof parseNumberToken(segments[4]) === 'number'
225
+ );
226
+ }
227
+
228
+ if (segments[1] === 'exact' && segments[2] === 'single' && segments[3] === 'date') {
229
+ return segments.length === 5 && /^v.+/.test(segments[4]);
230
+ }
231
+
232
+ if (segments[1] === 'exact' && segments[2] === 'range' && segments[3] === 'date') {
233
+ return segments.length === 6 && /^v.+/.test(segments[4]) && /^v.+/.test(segments[5]);
234
+ }
235
+
236
+ return false;
237
+ }
238
+
239
+ export function parseCtxDateExpression(value: unknown): any {
240
+ if (!isCtxDateExpression(value)) return undefined;
241
+ const segments = withDatePrefix(parseCtxDateSegments(value as string) || []);
242
+
243
+ if (segments[1] === 'preset' && segments.length === 3 && PRESET_KEYS.has(segments[2])) {
244
+ return { type: segments[2] };
245
+ }
246
+
247
+ if (
248
+ segments[1] === 'relative' &&
249
+ segments.length === 5 &&
250
+ RELATIVE_DIRECTIONS.has(segments[2]) &&
251
+ RELATIVE_UNITS.has(segments[3])
252
+ ) {
253
+ const amount = parseNumberToken(segments[4]);
254
+ if (typeof amount === 'number') {
255
+ return { type: segments[2], unit: segments[3], number: amount };
256
+ }
257
+ return undefined;
258
+ }
259
+
260
+ if (segments[1] === 'exact' && segments[2] === 'single' && segments[3] === 'date' && segments.length === 5) {
261
+ const raw = String(segments[4] || '');
262
+ if (!raw.startsWith('v')) return undefined;
263
+ return decodeBase64Url(raw.slice(1));
264
+ }
265
+
266
+ if (segments[1] === 'exact' && segments[2] === 'range' && segments[3] === 'date' && segments.length === 6) {
267
+ const leftRaw = String(segments[4] || '');
268
+ const rightRaw = String(segments[5] || '');
269
+ if (!leftRaw.startsWith('v') || !rightRaw.startsWith('v')) return undefined;
270
+ const left = decodeBase64Url(leftRaw.slice(1));
271
+ const right = decodeBase64Url(rightRaw.slice(1));
272
+ if (typeof left === 'undefined' || typeof right === 'undefined') return undefined;
273
+ return [left, right];
274
+ }
275
+
276
+ return undefined;
277
+ }
278
+
279
+ export function serializeCtxDateValue(value: unknown): string | undefined {
280
+ if (isCtxDateExpression(value)) {
281
+ return String(value).trim();
282
+ }
283
+
284
+ if (value == null || value === '') {
285
+ return undefined;
286
+ }
287
+
288
+ if (Array.isArray(value)) {
289
+ const start = normalizeToString(value[0]);
290
+ const end = normalizeToString(value[1]);
291
+ if (start && end) {
292
+ return toCtxDateExpression([
293
+ 'date',
294
+ 'exact',
295
+ 'range',
296
+ 'date',
297
+ `v${encodeBase64Url(start)}`,
298
+ `v${encodeBase64Url(end)}`,
299
+ ]);
300
+ }
301
+ if (start) {
302
+ return toCtxDateExpression(['date', 'exact', 'single', 'date', `v${encodeBase64Url(start)}`]);
303
+ }
304
+ return undefined;
305
+ }
306
+
307
+ if (typeof value === 'object' && value) {
308
+ const typed = value as { type?: unknown; unit?: unknown; number?: unknown };
309
+ const type = typeof typed.type === 'string' ? typed.type : '';
310
+
311
+ if (type === 'past' || type === 'next') {
312
+ const unit = typeof typed.unit === 'string' && RELATIVE_UNITS.has(typed.unit) ? typed.unit : 'day';
313
+ const rawNumber = Number(typed.number);
314
+ const number = Number.isFinite(rawNumber) && rawNumber > 0 ? Math.floor(rawNumber) : 1;
315
+ return toCtxDateExpression(['date', 'relative', type, unit, `n${number}`]);
316
+ }
317
+
318
+ if (PRESET_KEYS.has(type)) {
319
+ return toCtxDateExpression(['date', 'preset', type]);
320
+ }
321
+ }
322
+
323
+ const single = normalizeToString(value);
324
+ if (single) {
325
+ return toCtxDateExpression(['date', 'exact', 'single', 'date', `v${encodeBase64Url(single)}`]);
326
+ }
327
+ return undefined;
328
+ }
329
+
330
+ export function resolveCtxDatePath(pathSegments: string[]): any {
331
+ const segments = withDatePrefix((pathSegments || []).map((seg) => String(seg)));
332
+ if (segments[0] !== 'date') return undefined;
333
+
334
+ if (segments[1] === 'preset' && segments.length === 3) {
335
+ const key = segments[2];
336
+ if (key === 'now') {
337
+ return dayjs().toISOString();
338
+ }
339
+
340
+ const now = dayjs();
341
+
342
+ if (key === 'today') return now.format('YYYY-MM-DD');
343
+ if (key === 'yesterday') return now.add(-1, 'day').format('YYYY-MM-DD');
344
+ if (key === 'tomorrow') return now.add(1, 'day').format('YYYY-MM-DD');
345
+
346
+ if (key === 'thisWeek') return startOfIsoWeek(now).format('YYYY-MM-DD');
347
+ if (key === 'lastWeek') return startOfIsoWeek(now.add(-1, 'week')).format('YYYY-MM-DD');
348
+ if (key === 'nextWeek') return startOfIsoWeek(now.add(1, 'week')).format('YYYY-MM-DD');
349
+
350
+ if (key === 'thisMonth') return now.startOf('month').format('YYYY-MM-DD');
351
+ if (key === 'lastMonth') return now.add(-1, 'month').startOf('month').format('YYYY-MM-DD');
352
+ if (key === 'nextMonth') return now.add(1, 'month').startOf('month').format('YYYY-MM-DD');
353
+
354
+ if (key === 'thisQuarter') return startOfQuarter(now).format('YYYY-MM-DD');
355
+ if (key === 'lastQuarter') return startOfQuarter(now.add(-3, 'month')).format('YYYY-MM-DD');
356
+ if (key === 'nextQuarter') return startOfQuarter(now.add(3, 'month')).format('YYYY-MM-DD');
357
+
358
+ if (key === 'thisYear') return now.startOf('year').format('YYYY-MM-DD');
359
+ if (key === 'lastYear') return now.add(-1, 'year').startOf('year').format('YYYY-MM-DD');
360
+ if (key === 'nextYear') return now.add(1, 'year').startOf('year').format('YYYY-MM-DD');
361
+
362
+ return undefined;
363
+ }
364
+
365
+ if (
366
+ segments[1] === 'relative' &&
367
+ segments.length === 5 &&
368
+ RELATIVE_DIRECTIONS.has(segments[2]) &&
369
+ RELATIVE_UNITS.has(segments[3])
370
+ ) {
371
+ const amount = parseNumberToken(segments[4]);
372
+ if (typeof amount !== 'number') return undefined;
373
+ const direction = segments[2] === 'past' ? -1 : 1;
374
+ const unit = segments[3] as dayjs.ManipulateType;
375
+ return dayjs()
376
+ .add(direction * amount, unit)
377
+ .format('YYYY-MM-DD');
378
+ }
379
+
380
+ if (segments[1] === 'exact' && segments[2] === 'single' && segments[3] === 'date' && segments.length === 5) {
381
+ const token = String(segments[4] || '');
382
+ if (!token.startsWith('v')) return undefined;
383
+ return decodeBase64Url(token.slice(1));
384
+ }
385
+
386
+ if (segments[1] === 'exact' && segments[2] === 'range' && segments[3] === 'date' && segments.length === 6) {
387
+ const leftToken = String(segments[4] || '');
388
+ const rightToken = String(segments[5] || '');
389
+ if (!leftToken.startsWith('v') || !rightToken.startsWith('v')) return undefined;
390
+ const left = decodeBase64Url(leftToken.slice(1));
391
+ const right = decodeBase64Url(rightToken.slice(1));
392
+ if (typeof left === 'undefined' || typeof right === 'undefined') return undefined;
393
+ return [left, right];
394
+ }
395
+
396
+ return undefined;
397
+ }
@@ -34,3 +34,14 @@ export class FlowExitAllException extends Error {
34
34
  this.modelUid = modelUid;
35
35
  }
36
36
  }
37
+
38
+ /**
39
+ * 取消当前保存但保持设置弹窗打开
40
+ * 用于“保存前确认”场景,用户取消时不应关闭弹窗也不应提示错误
41
+ */
42
+ export class FlowCancelSaveException extends Error {
43
+ constructor(message = 'Flow settings save cancelled.') {
44
+ super(message);
45
+ this.name = 'FlowCancelSaveException';
46
+ }
47
+ }
@@ -22,7 +22,7 @@ export {
22
22
  export { escapeT, getT, tExpr } from './translation';
23
23
 
24
24
  // 异常类
25
- export { FlowExitException } from './exceptions';
25
+ export { FlowCancelSaveException, FlowExitException } from './exceptions';
26
26
 
27
27
  // 流程定义相关
28
28
  export { defineAction } from './flow-definitions';
@@ -34,7 +34,13 @@ export { isInheritedFrom } from './inheritance';
34
34
  export { resolveCreateModelOptions, resolveDefaultParams, resolveExpressions } from './params-resolvers';
35
35
 
36
36
  // Schema 工具
37
- export { compileUiSchema, resolveStepUiSchema, resolveUiMode, shouldHideStepInSettings } from './schema-utils';
37
+ export {
38
+ compileUiSchema,
39
+ resolveStepUiSchema,
40
+ resolveStepDisabledInSettings,
41
+ resolveUiMode,
42
+ shouldHideStepInSettings,
43
+ } from './schema-utils';
38
44
 
39
45
  // Runtime Context Steps 设置
40
46
  export { setupRuntimeContextSteps } from './setupRuntimeContextSteps';
@@ -59,9 +65,34 @@ export { extractPropertyPath, formatPathToVariable, isVariableExpression } from
59
65
 
60
66
  export { clearAutoFlowError, getAutoFlowError, setAutoFlowError, type AutoFlowError } from './autoFlowError';
61
67
  export { parsePathnameToViewParams, type ViewParam } from './parsePathnameToViewParams';
68
+ export {
69
+ decodeBase64Url,
70
+ encodeBase64Url,
71
+ isCompleteCtxDatePath,
72
+ isCtxDatePathPrefix,
73
+ isCtxDateExpression,
74
+ parseCtxDateExpression,
75
+ resolveCtxDatePath,
76
+ serializeCtxDateValue,
77
+ } from './dateVariable';
62
78
 
63
79
  // 安全全局对象(window/document)
64
- export { createSafeDocument, createSafeWindow, createSafeNavigator } from './safeGlobals';
80
+ export {
81
+ createSafeDocument,
82
+ createSafeWindow,
83
+ createSafeNavigator,
84
+ createSafeRunJSGlobals,
85
+ runjsWithSafeGlobals,
86
+ } from './safeGlobals';
87
+
88
+ // RunJS value helpers
89
+ export { isRunJSValue, normalizeRunJSValue, extractUsedVariablePathsFromRunJS, type RunJSValue } from './runjsValue';
90
+
91
+ // RunJS helpers
92
+ export { resolveRunJSObjectValues } from './resolveRunJSObjectValues';
93
+
94
+ // RunJS 代码兼容预处理({{ }})与 JSX 编译
95
+ export { prepareRunJsCode, preprocessRunJsTemplates } from './runjsTemplateCompat';
65
96
 
66
97
  // Ephemeral context helper(用于临时注入属性/方法,避免污染父级 ctx)
67
98
  export { createEphemeralContext } from './createEphemeralContext';
@@ -69,3 +100,6 @@ export { createEphemeralContext } from './createEphemeralContext';
69
100
  // Filter helpers
70
101
  export { pruneFilter } from './pruneFilter';
71
102
  export { isBeforeRenderFlow } from './flows';
103
+
104
+ // Module URL resolver
105
+ export { resolveModuleUrl, isCssFile } from './resolveModuleUrl';