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

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
@@ -0,0 +1,60 @@
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 { describe, expect, it, vi } from 'vitest';
11
+ import { FlowEngine } from '../../flowEngine';
12
+ import { SQLResource } from '../sqlResource';
13
+
14
+ function createSQLResource() {
15
+ const engine = new FlowEngine();
16
+ return engine.createResource(SQLResource);
17
+ }
18
+
19
+ describe('SQLResource - refresh', () => {
20
+ it('should coalesce multiple refresh calls and settle all awaiters', async () => {
21
+ vi.useFakeTimers();
22
+ try {
23
+ const r = createSQLResource();
24
+ const run = vi.fn().mockResolvedValue({ data: { ok: 1 }, meta: { page: 1 } });
25
+ (r as any).run = run;
26
+
27
+ const p1 = r.refresh();
28
+ const p2 = r.refresh();
29
+
30
+ await vi.runAllTimersAsync();
31
+ await expect(p1).resolves.toBeUndefined();
32
+ await expect(p2).resolves.toBeUndefined();
33
+ expect(run).toHaveBeenCalledTimes(1);
34
+ } finally {
35
+ vi.useRealTimers();
36
+ }
37
+ });
38
+
39
+ it('should reject all awaiters on failure', async () => {
40
+ vi.useFakeTimers();
41
+ try {
42
+ const r = createSQLResource();
43
+ const run = vi.fn().mockRejectedValue(new Error('boom'));
44
+ (r as any).run = run;
45
+
46
+ const p1 = r.refresh();
47
+ const p2 = r.refresh();
48
+
49
+ // Attach handlers before timers run to avoid unhandled rejection warnings.
50
+ const e1 = expect(p1).rejects.toThrow('boom');
51
+ const e2 = expect(p2).rejects.toThrow('boom');
52
+ await vi.runAllTimersAsync();
53
+ await e1;
54
+ await e2;
55
+ expect(run).toHaveBeenCalledTimes(1);
56
+ } finally {
57
+ vi.useRealTimers();
58
+ }
59
+ });
60
+ });
@@ -12,6 +12,7 @@ import _ from 'lodash';
12
12
  import { APIResource } from './apiResource';
13
13
  import { FilterItem } from './filterItem';
14
14
  import { ResourceError } from './flowResource';
15
+ import { DATA_SOURCE_DIRTY_EVENT } from '../views/viewEvents';
15
16
 
16
17
  export abstract class BaseRecordResource<TData = any> extends APIResource<TData> {
17
18
  protected resourceName: string;
@@ -136,6 +137,36 @@ export abstract class BaseRecordResource<TData = any> extends APIResource<TData>
136
137
  return this.resourceName;
137
138
  }
138
139
 
140
+ /**
141
+ * Mark current resource as dirty on the root FlowEngine.
142
+ * Used to coordinate "refresh on active" across view stacks.
143
+ */
144
+ protected markDataSourceDirty(resourceName?: string) {
145
+ const engine = this.context.engine;
146
+ if (!engine) return;
147
+
148
+ const dataSourceKey = this.getDataSourceKey() || 'main';
149
+ const resName = resourceName || this.getResourceName();
150
+ if (!resName) return;
151
+
152
+ const affectedResourceNames = new Set<string>([String(resName)]);
153
+ // Optional safety: association resources like "users.profile" may impact parent collection views.
154
+ if (typeof resName === 'string' && resName.includes('.')) {
155
+ affectedResourceNames.add(resName.split('.')[0]);
156
+ }
157
+
158
+ for (const name of affectedResourceNames) {
159
+ engine.markDataSourceDirty(dataSourceKey, name);
160
+ }
161
+
162
+ // Signal current view to re-evaluate dirty blocks (e.g., same-view sibling refresh).
163
+ // This is emitted on the *current* engine emitter (view-scoped) so it won't affect other views.
164
+ engine.emitter?.emit?.(DATA_SOURCE_DIRTY_EVENT, {
165
+ dataSourceKey,
166
+ resourceNames: Array.from(affectedResourceNames),
167
+ });
168
+ }
169
+
139
170
  setSourceId(sourceId: string | number) {
140
171
  this.sourceId = sourceId;
141
172
  return this;
@@ -16,6 +16,7 @@ export class MultiRecordResource<TDataItem = any> extends BaseRecordResource<TDa
16
16
  protected _data = observable.ref<TDataItem[]>([]);
17
17
  protected _meta = observable.ref<Record<string, any>>({});
18
18
  private refreshTimer: NodeJS.Timeout | null = null;
19
+ private refreshWaiters: Array<{ resolve: () => void; reject: (error: any) => void }> = [];
19
20
  protected createActionOptions = {};
20
21
  protected updateActionOptions = {};
21
22
  protected _refreshActionName = 'list';
@@ -113,6 +114,7 @@ export class MultiRecordResource<TDataItem = any> extends BaseRecordResource<TDa
113
114
  async create(data: TDataItem, options?: AxiosRequestConfig & { refresh?: boolean }): Promise<void> {
114
115
  const config = this.mergeRequestConfig({ data }, this.createActionOptions, options);
115
116
  const res = await this.runAction('create', config);
117
+ this.markDataSourceDirty();
116
118
  this.emit('saved', data);
117
119
  if (options?.refresh !== false) {
118
120
  await this.refresh();
@@ -145,6 +147,7 @@ export class MultiRecordResource<TDataItem = any> extends BaseRecordResource<TDa
145
147
  options,
146
148
  );
147
149
  await this.runAction('update', config);
150
+ this.markDataSourceDirty();
148
151
  this.emit('saved', data);
149
152
  await this.refresh();
150
153
  }
@@ -170,6 +173,7 @@ export class MultiRecordResource<TDataItem = any> extends BaseRecordResource<TDa
170
173
  options,
171
174
  );
172
175
  await this.runAction('destroy', config);
176
+ this.markDataSourceDirty();
173
177
  const currentPage = this.getPage();
174
178
  const lastPage = Math.ceil((this.getCount() - _.castArray(filterByTk).length) / this.getPageSize());
175
179
  if (currentPage > lastPage) {
@@ -197,7 +201,11 @@ export class MultiRecordResource<TDataItem = any> extends BaseRecordResource<TDa
197
201
 
198
202
  // 设置新的定时器,在下一个事件循环执行
199
203
  return new Promise<void>((resolve, reject) => {
204
+ this.refreshWaiters.push({ resolve, reject });
200
205
  this.refreshTimer = setTimeout(async () => {
206
+ const waiters = this.refreshWaiters;
207
+ this.refreshWaiters = [];
208
+ this.refreshTimer = null;
201
209
  try {
202
210
  this.clearError();
203
211
  this.loading = true;
@@ -213,13 +221,12 @@ export class MultiRecordResource<TDataItem = any> extends BaseRecordResource<TDa
213
221
  this.setPageSize(meta.pageSize);
214
222
  }
215
223
  this.emit('refresh');
216
- this.loading = false;
217
- resolve();
224
+ waiters.forEach((w) => w.resolve());
218
225
  } catch (error) {
219
226
  this.setError(error);
220
- reject(error instanceof Error ? error : new Error(String(error)));
227
+ const err = error instanceof Error ? error : new Error(String(error));
228
+ waiters.forEach((w) => w.reject(err));
221
229
  } finally {
222
- this.refreshTimer = null;
223
230
  this.loading = false;
224
231
  }
225
232
  });
@@ -44,6 +44,8 @@ export class SingleRecordResource<TData = any> extends BaseRecordResource<TData>
44
44
  ...config,
45
45
  data: result,
46
46
  });
47
+ // Mark as dirty before emitting/refreshing so other views can refresh when activated.
48
+ this.markDataSourceDirty();
47
49
  this.emit('saved', data);
48
50
  if (options?.refresh !== false) {
49
51
  await this.refresh();
@@ -61,6 +63,7 @@ export class SingleRecordResource<TData = any> extends BaseRecordResource<TData>
61
63
  options,
62
64
  );
63
65
  await this.runAction('destroy', config);
66
+ this.markDataSourceDirty();
64
67
  this.setData(null);
65
68
  }
66
69
 
@@ -27,10 +27,10 @@ type SQLSaveOptions = {
27
27
  };
28
28
 
29
29
  export class FlowSQLRepository {
30
- protected ctx: FlowEngineContext;
30
+ protected ctx: FlowContext;
31
31
 
32
- constructor(ctx: FlowEngineContext) {
33
- this.ctx = new FlowContext() as FlowEngineContext;
32
+ constructor(ctx: FlowContext) {
33
+ this.ctx = new FlowContext();
34
34
  this.ctx.addDelegate(ctx);
35
35
  this.ctx.defineProperty('offset', {
36
36
  get: () => 0,
@@ -110,6 +110,7 @@ export class SQLResource<TData = any> extends BaseRecordResource<TData> {
110
110
  protected _data = observable.ref<TData>(null);
111
111
  protected _meta = observable.ref<Record<string, any>>({});
112
112
  private refreshTimer: NodeJS.Timeout | null = null;
113
+ private refreshWaiters: Array<{ resolve: () => void; reject: (error: any) => void }> = [];
113
114
  private _debugEnabled = false;
114
115
  private _sql: string;
115
116
 
@@ -272,7 +273,11 @@ export class SQLResource<TData = any> extends BaseRecordResource<TData> {
272
273
 
273
274
  // 设置新的定时器,在下一个事件循环执行
274
275
  return new Promise<void>((resolve, reject) => {
276
+ this.refreshWaiters.push({ resolve, reject });
275
277
  this.refreshTimer = setTimeout(async () => {
278
+ const waiters = this.refreshWaiters;
279
+ this.refreshWaiters = [];
280
+ this.refreshTimer = null;
276
281
  try {
277
282
  this.clearError();
278
283
  this.loading = true;
@@ -281,12 +286,12 @@ export class SQLResource<TData = any> extends BaseRecordResource<TData> {
281
286
  this.setData(data).setMeta(meta);
282
287
  this.loading = false;
283
288
  this.emit('refresh');
284
- resolve();
289
+ waiters.forEach((w) => w.resolve());
285
290
  } catch (error) {
286
291
  this.setError(error);
287
- reject(error instanceof Error ? error : new Error(String(error)));
292
+ const err = error instanceof Error ? error : new Error(String(error));
293
+ waiters.forEach((w) => w.reject(err));
288
294
  } finally {
289
- this.refreshTimer = null;
290
295
  this.loading = false;
291
296
  }
292
297
  });
@@ -19,6 +19,11 @@ FormJSFieldItemRunJSContext.define({
19
19
  value: `Current field value (read-only in display mode; in controlled scenarios, use setProps to modify).`,
20
20
  record: `Current record data object (read-only).
21
21
  Contains all field values of the parent record.`,
22
+ formValues: {
23
+ description: 'Snapshot of current form values (object). Available in form contexts (CreateForm/EditForm).',
24
+ detail: 'Record<string, any>',
25
+ examples: ['const { name, status } = ctx.formValues || {};'],
26
+ },
22
27
  },
23
28
  methods: {
24
29
  onRefReady: `Wait for form field container DOM element to be ready before executing callback.
@@ -36,6 +41,11 @@ FormJSFieldItemRunJSContext.define(
36
41
  element: 'ElementProxy,表单字段容器',
37
42
  value: '字段值(展示模式为只读;受控场景用 setProps 修改)',
38
43
  record: '当前记录(只读)',
44
+ formValues: {
45
+ description: '当前表单值快照(对象)。仅表单相关上下文可用(Create/Edit Form)。',
46
+ detail: 'Record<string, any>',
47
+ examples: ['const { name, status } = ctx.formValues || {};'],
48
+ },
39
49
  },
40
50
  methods: {
41
51
  onRefReady: '容器就绪回调',
@@ -34,7 +34,9 @@ JSBlockRunJSContext.define({
34
34
  Parameters: (ref: React.RefObject, callback: (element: HTMLElement) => void, timeout?: number) => void
35
35
  Example: ctx.onRefReady(ctx.ref, (el) => { el.innerHTML = "Ready!" })`,
36
36
  requireAsync: 'Load external library: `const lib = await ctx.requireAsync(url)`',
37
- importAsync: 'Dynamically import ESM module: `const mod = await ctx.importAsync(url)`',
37
+ importAsync:
38
+ 'Dynamically import an ESM module by URL: `const mod = await ctx.importAsync(url)`.\n' +
39
+ 'Note: if the module has only a default export, ctx.importAsync returns that default value directly (no `.default`).',
38
40
  },
39
41
  });
40
42
 
@@ -57,7 +59,9 @@ JSBlockRunJSContext.define(
57
59
  methods: {
58
60
  onRefReady: '容器 ref 就绪回调:\n```js\nctx.onRefReady(ctx.ref, el => { /* ... */ })\n```',
59
61
  requireAsync: '加载外部库:`const lib = await ctx.requireAsync(url)`',
60
- importAsync: '按 URL 动态导入 ESM 模块:`const mod = await ctx.importAsync(url)`',
62
+ importAsync:
63
+ '按 URL 动态导入 ESM 模块:`const mod = await ctx.importAsync(url)`。\n' +
64
+ '注意:当模块只有 default 一个导出时,ctx.importAsync 会直接返回 default 值(无需再写 `.default`)。',
61
65
  },
62
66
  },
63
67
  { locale: 'zh-CN' },
@@ -0,0 +1,106 @@
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 { FlowRunJSContext } from '../../flowContext';
11
+
12
+ /**
13
+ * RunJS context for JSEditableFieldModel (form editable custom field).
14
+ * NOTE: Some APIs (e.g., getValue/setValue/element) are provided by the model's runtime handler.
15
+ * This doc is used for editor autocomplete and AI coding assistance.
16
+ */
17
+ export class JSEditableFieldRunJSContext extends FlowRunJSContext {}
18
+
19
+ JSEditableFieldRunJSContext.define({
20
+ label: 'JSEditableField RunJS context',
21
+ properties: {
22
+ element: {
23
+ description:
24
+ 'ElementProxy instance providing a safe DOM container for field rendering. In editable mode this container is typically a <span> element.',
25
+ detail: 'ElementProxy',
26
+ },
27
+ value: {
28
+ description:
29
+ 'Current field value (read-only snapshot). In editable scenarios, prefer ctx.getValue()/ctx.setValue(v) for two-way binding.',
30
+ detail: 'any',
31
+ examples: ['const v = ctx.getValue?.() ?? ctx.value;', 'ctx.setValue?.("new value");'],
32
+ },
33
+ record: {
34
+ description: 'Current record data object (read-only). Available in forms that are bound to a record.',
35
+ detail: 'Record<string, any>',
36
+ },
37
+ form: {
38
+ description: 'Ant Design Form instance for reading/writing other fields. Example: ctx.form.getFieldValue("name")',
39
+ detail: 'FormInstance',
40
+ },
41
+ formValues: {
42
+ description:
43
+ 'Snapshot of current form values (object). Prefer ctx.form.getFieldsValue() when you need the latest values.',
44
+ detail: 'Record<string, any>',
45
+ },
46
+ namePath: {
47
+ description: 'Field namePath in the form (array). Useful for advanced Form operations.',
48
+ detail: 'Array<string | number>',
49
+ },
50
+ disabled: 'Whether the field is disabled (boolean).',
51
+ readOnly: 'Whether the field is read-only (boolean).',
52
+ },
53
+ methods: {
54
+ getValue: {
55
+ description: 'Get current field value (recommended for editable custom fields).',
56
+ detail: '() => any',
57
+ completion: { insertText: 'ctx.getValue?.()' },
58
+ },
59
+ setValue: {
60
+ description: 'Set current field value (two-way binding with the form).',
61
+ detail: '(value: any) => void',
62
+ completion: { insertText: 'ctx.setValue?.(value)' },
63
+ examples: ['ctx.setValue?.(e.target.value);'],
64
+ },
65
+ },
66
+ });
67
+
68
+ JSEditableFieldRunJSContext.define(
69
+ {
70
+ label: 'JS 可编辑字段 RunJS 上下文',
71
+ properties: {
72
+ element: {
73
+ description: 'ElementProxy,字段渲染的安全容器(通常为 <span> 容器)。',
74
+ detail: 'ElementProxy',
75
+ },
76
+ value: {
77
+ description: '字段当前值(只读快照)。可编辑场景建议使用 ctx.getValue()/ctx.setValue(v) 做双向绑定。',
78
+ detail: 'any',
79
+ examples: ['const v = ctx.getValue?.() ?? ctx.value;', 'ctx.setValue?.("新值");'],
80
+ },
81
+ record: { description: '当前记录对象(只读;表单绑定记录时可用)。', detail: 'Record<string, any>' },
82
+ form: { description: 'Ant Design Form 实例,可读写其它字段。', detail: 'FormInstance' },
83
+ formValues: {
84
+ description: '当前表单值快照(对象)。需要最新值可用 ctx.form.getFieldsValue()。',
85
+ detail: 'Record<string, any>',
86
+ },
87
+ namePath: { description: '字段在表单中的 namePath(数组)。', detail: 'Array<string | number>' },
88
+ disabled: '是否禁用(boolean)',
89
+ readOnly: '是否只读(boolean)',
90
+ },
91
+ methods: {
92
+ getValue: {
93
+ description: '获取字段当前值(可编辑字段推荐使用)。',
94
+ detail: '() => any',
95
+ completion: { insertText: 'ctx.getValue?.()' },
96
+ },
97
+ setValue: {
98
+ description: '设置字段当前值(与表单双向绑定)。',
99
+ detail: '(value: any) => void',
100
+ completion: { insertText: 'ctx.setValue?.(value)' },
101
+ examples: ['ctx.setValue?.(e.target.value);'],
102
+ },
103
+ },
104
+ },
105
+ { locale: 'zh-CN' },
106
+ );
@@ -20,6 +20,11 @@ JSItemRunJSContext.define({
20
20
  Provides access to the data resource associated with the current form context.`,
21
21
  record: `Current record data object (read-only).
22
22
  Contains all field values of the parent record.`,
23
+ formValues: {
24
+ description: 'Snapshot of current form values (object). Available in form contexts (CreateForm/EditForm).',
25
+ detail: 'Record<string, any>',
26
+ examples: ['const { name, status } = ctx.formValues || {};'],
27
+ },
23
28
  },
24
29
  methods: {
25
30
  onRefReady: `Wait for form item container DOM element to be ready before executing callback.
@@ -34,6 +39,11 @@ JSItemRunJSContext.define(
34
39
  element: 'ElementProxy,表单项渲染容器,支持 innerHTML/append 等 DOM 操作',
35
40
  resource: '当前资源(只读)',
36
41
  record: '当前记录(只读)',
42
+ formValues: {
43
+ description: '当前表单值快照(对象)。仅表单相关上下文可用(Create/Edit Form)。',
44
+ detail: 'Record<string, any>',
45
+ examples: ['const { name, status } = ctx.formValues || {};'],
46
+ },
37
47
  },
38
48
  methods: {
39
49
  onRefReady: '容器就绪后执行回调。参数:(ref, callback, timeout?)',