@nocobase/flow-engine 2.0.0-alpha.4 → 2.0.0-alpha.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/BlockScopedFlowEngine.d.ts +23 -0
- package/lib/BlockScopedFlowEngine.js +90 -0
- package/lib/FlowContextProvider.d.ts +2 -2
- package/lib/FlowContextProvider.js +3 -3
- package/lib/FlowDefinition.d.ts +4 -2
- package/lib/JSRunner.js +3 -0
- package/lib/ViewScopedFlowEngine.d.ts +1 -1
- package/lib/ViewScopedFlowEngine.js +12 -0
- package/lib/acl/Acl.d.ts +1 -0
- package/lib/acl/Acl.js +11 -0
- package/lib/components/FieldModelRenderer.js +14 -6
- package/lib/components/FieldSkeleton.d.ts +10 -0
- package/lib/components/FieldSkeleton.js +64 -0
- package/lib/components/FlowContextSelector.js +7 -2
- package/lib/components/FlowModelRenderer.d.ts +3 -6
- package/lib/components/FlowModelRenderer.js +16 -47
- package/lib/components/FormItem.js +5 -1
- package/lib/components/MobilePopup.d.ts +20 -0
- package/lib/components/MobilePopup.js +102 -0
- package/lib/components/MobilePopup.style.d.ts +17 -0
- package/lib/components/MobilePopup.style.js +186 -0
- package/lib/components/index.d.ts +1 -0
- package/lib/components/index.js +3 -1
- package/lib/components/settings/independents/dropdown/FlowsDropdownButton.js +2 -2
- package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.js +17 -5
- package/lib/components/settings/wrappers/contextual/FlowsContextMenu.js +2 -2
- package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.d.ts +2 -2
- package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.js +35 -6
- package/lib/components/settings/wrappers/contextual/StepSettingsDialog.js +7 -1
- package/lib/components/variables/VariableInput.js +19 -5
- package/lib/components/variables/VariableTag.js +43 -2
- package/lib/components/variables/types.d.ts +2 -0
- package/lib/data-source/index.d.ts +15 -4
- package/lib/data-source/index.js +42 -13
- package/lib/data-source/sortCollectionsByInherits.d.ts +10 -0
- package/lib/data-source/sortCollectionsByInherits.js +71 -0
- package/lib/emitter.d.ts +6 -0
- package/lib/emitter.js +12 -0
- package/lib/executor/FlowExecutor.d.ts +4 -5
- package/lib/executor/FlowExecutor.js +168 -99
- package/lib/flow-registry/GlobalFlowRegistry.d.ts +1 -0
- package/lib/flow-registry/GlobalFlowRegistry.js +3 -0
- package/lib/flow-registry/InstanceFlowRegistry.d.ts +1 -0
- package/lib/flow-registry/InstanceFlowRegistry.js +3 -0
- package/lib/flowContext.d.ts +37 -5
- package/lib/flowContext.js +224 -87
- package/lib/flowEngine.d.ts +24 -1
- package/lib/flowEngine.js +81 -8
- package/lib/flowSettings.d.ts +2 -1
- package/lib/flowSettings.js +16 -8
- package/lib/hooks/useApplyAutoFlows.js +2 -1
- package/lib/index.d.ts +7 -1
- package/lib/index.js +32 -3
- package/lib/locale/en-US.json +4 -2
- package/lib/locale/index.d.ts +4 -0
- package/lib/locale/zh-CN.json +4 -2
- package/lib/models/CollectionFieldModel.d.ts +3 -0
- package/lib/models/CollectionFieldModel.js +52 -6
- package/lib/models/flowModel.d.ts +30 -29
- package/lib/models/flowModel.js +153 -100
- package/lib/models/forkFlowModel.d.ts +8 -4
- package/lib/models/forkFlowModel.js +38 -8
- package/lib/provider.d.ts +3 -1
- package/lib/provider.js +7 -5
- package/lib/resources/multiRecordResource.js +25 -1
- package/lib/resources/singleRecordResource.js +19 -1
- package/lib/resources/sqlResource.d.ts +1 -0
- package/lib/resources/sqlResource.js +20 -24
- package/lib/runjs-context/contexts/FormJSFieldItemRunJSContext.d.ts +1 -6
- package/lib/runjs-context/contexts/FormJSFieldItemRunJSContext.js +27 -20
- package/lib/runjs-context/contexts/JSBlockRunJSContext.d.ts +1 -6
- package/lib/runjs-context/contexts/JSBlockRunJSContext.js +46 -33
- package/lib/runjs-context/contexts/JSCollectionActionRunJSContext.d.ts +1 -2
- package/lib/runjs-context/contexts/JSCollectionActionRunJSContext.js +14 -15
- package/lib/runjs-context/contexts/{LinkageRunJSContext.d.ts → JSColumnRunJSContext.d.ts} +6 -3
- package/lib/runjs-context/contexts/JSColumnRunJSContext.js +78 -0
- package/lib/runjs-context/contexts/JSFieldRunJSContext.d.ts +1 -6
- package/lib/runjs-context/contexts/JSFieldRunJSContext.js +28 -24
- package/lib/runjs-context/contexts/JSItemRunJSContext.d.ts +1 -6
- package/lib/runjs-context/contexts/JSItemRunJSContext.js +24 -20
- package/lib/runjs-context/contexts/JSRecordActionRunJSContext.d.ts +1 -2
- package/lib/runjs-context/contexts/JSRecordActionRunJSContext.js +16 -17
- package/lib/runjs-context/contexts/base.d.ts +9 -0
- package/lib/runjs-context/contexts/base.js +205 -0
- package/lib/runjs-context/helpers.d.ts +5 -2
- package/lib/runjs-context/helpers.js +36 -27
- package/lib/runjs-context/registry.d.ts +7 -4
- package/lib/runjs-context/registry.js +10 -42
- package/lib/runjs-context/setup.d.ts +9 -0
- package/lib/runjs-context/setup.js +82 -0
- package/lib/runjs-context/snippets/global/{copy-record-json.snippet.js → api-request.snippet.js} +25 -10
- package/lib/runjs-context/snippets/global/clipboard-copy-text.snippet.js +61 -0
- package/lib/runjs-context/snippets/global/{view-navigation-push.snippet.js → import-esm.snippet.js} +26 -12
- package/lib/runjs-context/snippets/global/message-error.snippet.js +6 -0
- package/lib/runjs-context/snippets/global/message-success.snippet.js +6 -0
- package/lib/runjs-context/snippets/global/notification-open.snippet.d.ts +3 -8
- package/lib/runjs-context/snippets/global/notification-open.snippet.js +8 -1
- package/lib/runjs-context/snippets/global/open-view-dialog.snippet.js +10 -3
- package/lib/runjs-context/snippets/global/open-view-drawer.snippet.js +10 -3
- package/lib/runjs-context/snippets/global/query-selector.snippet.js +53 -0
- package/lib/runjs-context/snippets/global/require-amd.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/global/{requireAsync.snippet.js → require-amd.snippet.js} +16 -13
- package/lib/runjs-context/snippets/global/window-open.snippet.d.ts +3 -8
- package/lib/runjs-context/snippets/global/window-open.snippet.js +8 -1
- package/lib/runjs-context/snippets/index.d.ts +14 -3
- package/lib/runjs-context/snippets/index.js +162 -40
- package/lib/runjs-context/snippets/scene/block/add-event-listener.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/{jsblock → block}/add-event-listener.snippet.js +11 -2
- package/lib/runjs-context/snippets/scene/block/api-fetch-render-list.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/block/api-fetch-render-list.snippet.js +64 -0
- package/lib/runjs-context/snippets/scene/block/chartjs-bar.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/block/chartjs-bar.snippet.js +99 -0
- package/lib/runjs-context/snippets/{libs → scene/block}/echarts-init.snippet.js +24 -7
- package/lib/runjs-context/snippets/scene/block/render-antd-icons.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/block/render-antd-icons.snippet.js +65 -0
- package/lib/runjs-context/snippets/scene/block/render-button-handler.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/{jsblock → block}/render-button-handler.snippet.js +17 -9
- package/lib/runjs-context/snippets/scene/block/render-iframe.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/block/render-iframe.snippet.js +57 -0
- package/lib/runjs-context/snippets/scene/block/render-info-card.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/block/render-info-card.snippet.js +71 -0
- package/lib/runjs-context/snippets/scene/block/render-react-jsx.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/{jsblock/render-card.snippet.js → block/render-react-jsx.snippet.js} +26 -13
- package/lib/runjs-context/snippets/scene/block/render-react.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/{jsblock → block}/render-react.snippet.js +18 -17
- package/lib/runjs-context/snippets/scene/block/render-statistics.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/block/render-statistics.snippet.js +95 -0
- package/lib/runjs-context/snippets/scene/block/render-timeline.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/block/render-timeline.snippet.js +84 -0
- package/lib/runjs-context/snippets/scene/block/resource-example.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/block/resource-example.snippet.js +60 -0
- package/lib/runjs-context/snippets/scene/block/three-users-orbit.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/block/three-users-orbit.snippet.js +283 -0
- package/lib/runjs-context/snippets/scene/block/vue-component.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/block/vue-component.snippet.js +124 -0
- package/lib/runjs-context/snippets/scene/detail/color-by-value.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/{jsfield → detail}/color-by-value.snippet.js +13 -3
- package/lib/runjs-context/snippets/scene/detail/copy-to-clipboard.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/{global → scene/detail}/copy-to-clipboard.snippet.js +28 -6
- package/lib/runjs-context/snippets/scene/detail/format-number.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/{jsfield → detail}/format-number.snippet.js +13 -3
- package/lib/runjs-context/snippets/scene/detail/innerHTML-value.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/{jsfield → detail}/innerHTML-value.snippet.js +13 -3
- package/lib/runjs-context/snippets/scene/detail/percentage-bar.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/detail/percentage-bar.snippet.js +82 -0
- package/lib/runjs-context/snippets/scene/detail/relative-time.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/detail/relative-time.snippet.js +80 -0
- package/lib/runjs-context/snippets/scene/detail/status-tag.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/detail/status-tag.snippet.js +74 -0
- package/lib/runjs-context/snippets/scene/form/calculate-total.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/form/calculate-total.snippet.js +63 -0
- package/lib/runjs-context/snippets/scene/form/cascade-select.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/form/cascade-select.snippet.js +81 -0
- package/lib/runjs-context/snippets/scene/form/conditional-required.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/form/conditional-required.snippet.js +64 -0
- package/lib/runjs-context/snippets/scene/form/copy-field-values.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/form/copy-field-values.snippet.js +74 -0
- package/lib/runjs-context/snippets/scene/form/render-basic.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/{jsitem → form}/render-basic.snippet.js +11 -2
- package/lib/runjs-context/snippets/scene/form/set-disabled.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/{linkage → form}/set-disabled.snippet.js +12 -3
- package/lib/runjs-context/snippets/scene/form/set-field-value.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/{linkage → form}/set-field-value.snippet.js +12 -3
- package/lib/runjs-context/snippets/scene/form/set-required.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/{linkage → form}/set-required.snippet.js +12 -3
- package/lib/runjs-context/snippets/scene/form/toggle-multiple-fields.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/form/toggle-multiple-fields.snippet.js +67 -0
- package/lib/runjs-context/snippets/scene/form/toggle-visible.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/{linkage → form}/toggle-visible.snippet.js +12 -3
- package/lib/runjs-context/snippets/scene/table/cell-open-dialog.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/table/cell-open-dialog.snippet.js +64 -0
- package/lib/runjs-context/snippets/scene/table/collection-selected-count.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/{actions → table}/collection-selected-count.snippet.js +11 -2
- package/lib/runjs-context/snippets/scene/table/concat-fields.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/table/concat-fields.snippet.js +79 -0
- package/lib/runjs-context/snippets/scene/table/destroy-selected.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/{global/log-json-record.snippet.js → scene/table/destroy-selected.snippet.js} +24 -11
- package/lib/runjs-context/snippets/scene/table/export-selected-json.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/table/export-selected-json.snippet.js +64 -0
- package/lib/runjs-context/snippets/scene/table/iterate-selected-rows.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/{actions → table}/iterate-selected-rows.snippet.js +11 -2
- package/lib/runjs-context/snippets/types.d.ts +9 -1
- package/lib/scheduler/ModelOperationScheduler.d.ts +51 -0
- package/lib/scheduler/ModelOperationScheduler.js +262 -0
- package/lib/types.d.ts +28 -3
- package/lib/types.js +4 -3
- package/lib/utils/associationObjectVariable.d.ts +32 -0
- package/lib/utils/associationObjectVariable.js +157 -0
- package/lib/utils/buildSettingsViewInputArgs.d.ts +19 -0
- package/lib/utils/buildSettingsViewInputArgs.js +75 -0
- package/lib/utils/createEphemeralContext.d.ts +13 -0
- package/lib/utils/createEphemeralContext.js +140 -0
- package/lib/utils/flows.d.ts +10 -0
- package/lib/{runjs-context/snippets/global/api-request-post.snippet.js → utils/flows.js} +21 -15
- package/lib/utils/index.d.ts +7 -2
- package/lib/utils/index.js +21 -2
- package/lib/utils/jsxTransform.d.ts +15 -0
- package/lib/utils/jsxTransform.js +68 -0
- package/lib/utils/params-resolvers.js +3 -3
- package/lib/utils/parsePathnameToViewParams.d.ts +1 -1
- package/lib/utils/parsePathnameToViewParams.js +41 -5
- package/lib/utils/pruneFilter.d.ts +21 -0
- package/lib/{runjs-context/snippets/global/try-catch-async.snippet.js → utils/pruneFilter.js} +24 -16
- package/lib/utils/safeGlobals.d.ts +5 -3
- package/lib/utils/safeGlobals.js +40 -0
- package/lib/utils/schema-utils.js +2 -2
- package/lib/utils/serverContextParams.d.ts +1 -0
- package/lib/utils/translation.d.ts +4 -1
- package/lib/utils/translation.js +6 -2
- package/lib/utils/variablesParams.d.ts +22 -5
- package/lib/utils/variablesParams.js +132 -36
- package/lib/views/DrawerComponent.js +21 -2
- package/lib/views/PageComponent.js +2 -1
- package/lib/views/ViewNavigation.d.ts +3 -9
- package/lib/views/ViewNavigation.js +16 -2
- package/lib/views/createViewMeta.d.ts +29 -1
- package/lib/views/createViewMeta.js +338 -72
- package/lib/views/index.d.ts +1 -0
- package/lib/views/index.js +3 -0
- package/lib/views/useDialog.d.ts +9 -8
- package/lib/views/useDialog.js +19 -14
- package/lib/views/useDrawer.d.ts +9 -8
- package/lib/views/useDrawer.js +54 -35
- package/lib/views/usePage.d.ts +9 -8
- package/lib/views/usePage.js +20 -14
- package/package.json +6 -3
- package/src/BlockScopedFlowEngine.ts +86 -0
- package/src/FlowContextProvider.tsx +4 -2
- package/src/JSRunner.ts +3 -0
- package/src/ViewScopedFlowEngine.ts +15 -1
- package/src/__tests__/JSRunner.test.ts +62 -53
- package/src/__tests__/blockScopedFlowEngine.test.ts +154 -0
- package/src/__tests__/createViewMeta.popup.test.ts +132 -0
- package/src/__tests__/flow-engine.test.ts +3 -0
- package/src/__tests__/flowContext.test.ts +52 -0
- package/src/__tests__/flowContextCreateJSRunner.test.ts +163 -0
- package/src/__tests__/flowEngine.saveModel.test.ts +4 -0
- package/src/__tests__/flowRunJSContextDefine.test.ts +508 -0
- package/src/__tests__/flowSettings.open.test.tsx +2 -0
- package/src/__tests__/flowSettings.test.ts +2 -0
- package/src/__tests__/globalFlowRegistry.test.ts +1 -1
- package/src/__tests__/modelOperationScheduler.test.ts +346 -0
- package/src/__tests__/objectVariable.test.ts +464 -0
- package/src/__tests__/runjsContext.test.ts +216 -35
- package/src/__tests__/runjsContextImplementations.test.ts +217 -0
- package/src/__tests__/runjsContextRuntime.test.ts +269 -0
- package/src/__tests__/runjsEdgeCases.test.ts +281 -0
- package/src/__tests__/runjsLocales.test.ts +36 -0
- package/src/__tests__/runjsRuntimeFeatures.test.ts +461 -0
- package/src/__tests__/runjsSnippets.test.ts +140 -0
- package/src/__tests__/viewScopedFlowEngine.test.ts +3 -3
- package/src/acl/Acl.tsx +14 -0
- package/src/acl/__tests__/Acl.test.tsx +43 -0
- package/src/components/DynamicFlowsEditor.tsx +3 -4
- package/src/components/FieldModelRenderer.tsx +20 -7
- package/src/components/FieldSkeleton.tsx +27 -0
- package/src/components/FlowContextSelector.tsx +6 -2
- package/src/components/FlowModelRenderer.tsx +33 -81
- package/src/components/FormItem.tsx +8 -1
- package/src/components/MobilePopup.style.ts +220 -0
- package/src/components/MobilePopup.tsx +86 -0
- package/src/components/__tests__/flow-model-render-error-fallback.test.tsx +6 -6
- package/src/components/index.ts +1 -0
- package/src/components/settings/independents/dropdown/FlowsDropdownButton.tsx +1 -1
- package/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +17 -4
- package/src/components/settings/wrappers/contextual/FlowsContextMenu.tsx +1 -1
- package/src/components/settings/wrappers/contextual/FlowsFloatContextMenu.tsx +39 -10
- package/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx +14 -1
- package/src/components/settings/wrappers/contextual/__tests__/DefaultSettingsIcon.test.tsx +425 -0
- package/src/components/subModel/__tests__/AddSubModelButton.test.tsx +5 -7
- package/src/components/variables/VariableInput.tsx +25 -5
- package/src/components/variables/VariableTag.tsx +54 -2
- package/src/components/variables/types.ts +2 -0
- package/src/data-source/__tests__/sortCollectionsByInherits.test.ts +125 -0
- package/src/data-source/index.ts +42 -11
- package/src/data-source/sortCollectionsByInherits.ts +61 -0
- package/src/emitter.ts +14 -0
- package/src/executor/FlowExecutor.ts +213 -119
- package/src/executor/__tests__/ctx-defs-injection.test.ts +197 -0
- package/src/executor/__tests__/flowExecutor.test.ts +151 -5
- package/src/flow-registry/GlobalFlowRegistry.ts +1 -0
- package/src/flow-registry/InstanceFlowRegistry.ts +1 -0
- package/src/flow-registry/__tests__/globalFlowRegistry.test.ts +54 -0
- package/src/flowContext.ts +303 -104
- package/src/flowEngine.ts +96 -6
- package/src/flowSettings.ts +12 -4
- package/src/hooks/useApplyAutoFlows.ts +3 -1
- package/src/index.ts +12 -1
- package/src/locale/en-US.json +4 -2
- package/src/locale/zh-CN.json +4 -2
- package/src/models/CollectionFieldModel.tsx +60 -8
- package/src/models/__tests__/dispatchEvent.behavior.test.ts +169 -0
- package/src/models/__tests__/flowModel.getFlows.sort.test.ts +33 -9
- package/src/models/__tests__/flowModel.scheduleModelOperation.test.tsx +129 -0
- package/src/models/__tests__/flowModel.test.ts +234 -111
- package/src/models/__tests__/forkFlowModel.test.ts +40 -7
- package/src/models/flowModel.tsx +197 -132
- package/src/models/forkFlowModel.ts +48 -8
- package/src/provider.tsx +10 -7
- package/src/resources/multiRecordResource.ts +28 -1
- package/src/resources/singleRecordResource.ts +19 -1
- package/src/resources/sqlResource.ts +20 -25
- package/src/runjs-context/contexts/FormJSFieldItemRunJSContext.ts +28 -21
- package/src/runjs-context/contexts/JSBlockRunJSContext.ts +46 -34
- package/src/runjs-context/contexts/JSCollectionActionRunJSContext.ts +15 -16
- package/src/runjs-context/contexts/JSColumnRunJSContext.ts +58 -0
- package/src/runjs-context/contexts/JSFieldRunJSContext.ts +30 -25
- package/src/runjs-context/contexts/JSItemRunJSContext.ts +25 -21
- package/src/runjs-context/contexts/JSRecordActionRunJSContext.ts +17 -18
- package/src/runjs-context/contexts/base.ts +196 -0
- package/src/runjs-context/helpers.ts +32 -30
- package/src/runjs-context/registry.ts +16 -47
- package/src/runjs-context/setup.ts +51 -0
- package/src/runjs-context/snippets/global/api-request.snippet.ts +38 -0
- package/src/runjs-context/snippets/global/clipboard-copy-text.snippet.ts +42 -0
- package/src/runjs-context/snippets/global/import-esm.snippet.ts +39 -0
- package/src/runjs-context/snippets/global/message-error.snippet.ts +6 -0
- package/src/runjs-context/snippets/global/message-success.snippet.ts +6 -0
- package/src/runjs-context/snippets/global/notification-open.snippet.ts +11 -1
- package/src/runjs-context/snippets/global/open-view-dialog.snippet.ts +10 -3
- package/src/runjs-context/snippets/global/open-view-drawer.snippet.ts +10 -3
- package/src/runjs-context/snippets/global/query-selector.snippet.ts +34 -0
- package/src/runjs-context/snippets/global/require-amd.snippet.ts +30 -0
- package/src/runjs-context/snippets/global/window-open.snippet.ts +11 -1
- package/src/runjs-context/snippets/index.ts +178 -39
- package/src/runjs-context/snippets/scene/{jsblock → block}/add-event-listener.snippet.ts +14 -2
- package/src/runjs-context/snippets/scene/block/api-fetch-render-list.snippet.ts +45 -0
- package/src/runjs-context/snippets/scene/block/chartjs-bar.snippet.ts +80 -0
- package/src/runjs-context/snippets/scene/block/echarts-init.snippet.ts +44 -0
- package/src/runjs-context/snippets/scene/block/render-antd-icons.snippet.ts +46 -0
- package/src/runjs-context/snippets/scene/block/render-button-handler.snippet.ts +35 -0
- package/src/runjs-context/snippets/scene/block/render-iframe.snippet.ts +38 -0
- package/src/runjs-context/snippets/scene/block/render-info-card.snippet.ts +52 -0
- package/src/runjs-context/snippets/scene/block/render-react-jsx.snippet.ts +39 -0
- package/src/runjs-context/snippets/scene/block/render-react.snippet.ts +38 -0
- package/src/runjs-context/snippets/scene/block/render-statistics.snippet.ts +76 -0
- package/src/runjs-context/snippets/scene/block/render-timeline.snippet.ts +65 -0
- package/src/runjs-context/snippets/scene/block/resource-example.snippet.ts +46 -0
- package/src/runjs-context/snippets/scene/block/three-users-orbit.snippet.ts +264 -0
- package/src/runjs-context/snippets/scene/block/vue-component.snippet.ts +105 -0
- package/src/runjs-context/snippets/scene/detail/color-by-value.snippet.ts +33 -0
- package/src/runjs-context/snippets/scene/detail/copy-to-clipboard.snippet.ts +45 -0
- package/src/runjs-context/snippets/scene/detail/format-number.snippet.ts +32 -0
- package/src/runjs-context/snippets/scene/detail/innerHTML-value.snippet.ts +31 -0
- package/src/runjs-context/snippets/scene/detail/percentage-bar.snippet.ts +63 -0
- package/src/runjs-context/snippets/scene/detail/relative-time.snippet.ts +61 -0
- package/src/runjs-context/snippets/scene/detail/status-tag.snippet.ts +55 -0
- package/src/runjs-context/snippets/scene/form/calculate-total.snippet.ts +44 -0
- package/src/runjs-context/snippets/scene/form/cascade-select.snippet.ts +62 -0
- package/src/runjs-context/snippets/scene/form/conditional-required.snippet.ts +45 -0
- package/src/runjs-context/snippets/scene/form/copy-field-values.snippet.ts +55 -0
- package/src/runjs-context/snippets/scene/{jsitem → form}/render-basic.snippet.ts +14 -2
- package/src/runjs-context/snippets/scene/{linkage → form}/set-disabled.snippet.ts +15 -3
- package/src/runjs-context/snippets/scene/{linkage → form}/set-field-value.snippet.ts +15 -3
- package/src/runjs-context/snippets/scene/{linkage → form}/set-required.snippet.ts +15 -3
- package/src/runjs-context/snippets/scene/form/toggle-multiple-fields.snippet.ts +48 -0
- package/src/runjs-context/snippets/scene/{linkage → form}/toggle-visible.snippet.ts +15 -3
- package/src/runjs-context/snippets/scene/table/cell-open-dialog.snippet.ts +45 -0
- package/src/runjs-context/snippets/scene/{actions → table}/collection-selected-count.snippet.ts +14 -2
- package/src/runjs-context/snippets/scene/table/concat-fields.snippet.ts +60 -0
- package/src/runjs-context/snippets/scene/table/destroy-selected.snippet.ts +36 -0
- package/src/runjs-context/snippets/scene/table/export-selected-json.snippet.ts +45 -0
- package/src/runjs-context/snippets/scene/{actions → table}/iterate-selected-rows.snippet.ts +14 -2
- package/src/runjs-context/snippets/types.ts +5 -1
- package/src/scheduler/ModelOperationScheduler.ts +304 -0
- package/src/types.ts +34 -0
- package/src/utils/__tests__/flows.test.ts +65 -0
- package/src/utils/__tests__/jsxTransform.test.ts +38 -0
- package/src/utils/__tests__/parsePathnameToViewParams.test.ts +25 -0
- package/src/utils/__tests__/pruneFilter.test.ts +38 -0
- package/src/utils/__tests__/safeGlobals.test.ts +22 -1
- package/src/utils/__tests__/variablesParams.test.ts +63 -0
- package/src/utils/associationObjectVariable.ts +180 -0
- package/src/utils/buildSettingsViewInputArgs.ts +72 -0
- package/src/utils/createEphemeralContext.ts +142 -0
- package/src/utils/flows.ts +23 -0
- package/src/utils/index.ts +11 -2
- package/src/utils/jsxTransform.ts +39 -0
- package/src/utils/params-resolvers.ts +2 -2
- package/src/utils/parsePathnameToViewParams.ts +50 -6
- package/src/utils/pruneFilter.ts +41 -0
- package/src/utils/safeGlobals.ts +49 -3
- package/src/utils/schema-utils.ts +1 -1
- package/src/utils/serverContextParams.ts +1 -0
- package/src/utils/translation.ts +7 -2
- package/src/utils/variablesParams.ts +156 -38
- package/src/views/DrawerComponent.tsx +21 -2
- package/src/views/PageComponent.tsx +1 -1
- package/src/views/ViewNavigation.ts +20 -12
- package/src/views/__tests__/ViewNavigation.test.ts +10 -0
- package/src/views/createViewMeta.ts +393 -70
- package/src/views/index.tsx +1 -0
- package/src/views/useDialog.tsx +24 -18
- package/src/views/useDrawer.tsx +74 -45
- package/src/views/usePage.tsx +26 -17
- package/lib/runjs-context/contexts/FlowRunJSContext.d.ts +0 -38
- package/lib/runjs-context/contexts/FlowRunJSContext.js +0 -217
- package/lib/runjs-context/contexts/LinkageRunJSContext.js +0 -62
- package/lib/runjs-context/index.d.ts +0 -19
- package/lib/runjs-context/index.js +0 -57
- package/lib/runjs-context/snippets/global/api-request-get.snippet.d.ts +0 -16
- package/lib/runjs-context/snippets/global/api-request-get.snippet.js +0 -42
- package/lib/runjs-context/snippets/global/api-request-post.snippet.d.ts +0 -16
- package/lib/runjs-context/snippets/global/console-log-ctx.snippet.d.ts +0 -16
- package/lib/runjs-context/snippets/global/console-log-ctx.snippet.js +0 -41
- package/lib/runjs-context/snippets/global/requireAsync.snippet.d.ts +0 -16
- package/lib/runjs-context/snippets/global/sleep.snippet.d.ts +0 -16
- package/lib/runjs-context/snippets/global/sleep.snippet.js +0 -43
- package/lib/runjs-context/snippets/global/try-catch-async.snippet.d.ts +0 -16
- package/lib/runjs-context/snippets/libs/echarts-init.snippet.d.ts +0 -15
- package/lib/runjs-context/snippets/scene/actions/collection-selected-count.snippet.d.ts +0 -15
- package/lib/runjs-context/snippets/scene/actions/iterate-selected-rows.snippet.d.ts +0 -15
- package/lib/runjs-context/snippets/scene/actions/record-id-message.snippet.d.ts +0 -15
- package/lib/runjs-context/snippets/scene/actions/record-id-message.snippet.js +0 -43
- package/lib/runjs-context/snippets/scene/actions/run-action-basic.snippet.d.ts +0 -15
- package/lib/runjs-context/snippets/scene/actions/run-action-basic.snippet.js +0 -40
- package/lib/runjs-context/snippets/scene/jsblock/add-event-listener.snippet.d.ts +0 -15
- package/lib/runjs-context/snippets/scene/jsblock/append-style.snippet.d.ts +0 -15
- package/lib/runjs-context/snippets/scene/jsblock/append-style.snippet.js +0 -42
- package/lib/runjs-context/snippets/scene/jsblock/jsx-mount.snippet.d.ts +0 -15
- package/lib/runjs-context/snippets/scene/jsblock/jsx-mount.snippet.js +0 -46
- package/lib/runjs-context/snippets/scene/jsblock/jsx-unmount.snippet.d.ts +0 -15
- package/lib/runjs-context/snippets/scene/jsblock/jsx-unmount.snippet.js +0 -41
- package/lib/runjs-context/snippets/scene/jsblock/render-basic.snippet.d.ts +0 -15
- package/lib/runjs-context/snippets/scene/jsblock/render-basic.snippet.js +0 -41
- package/lib/runjs-context/snippets/scene/jsblock/render-button-handler.snippet.d.ts +0 -15
- package/lib/runjs-context/snippets/scene/jsblock/render-react.snippet.d.ts +0 -15
- package/lib/runjs-context/snippets/scene/jsfield/color-by-value.snippet.d.ts +0 -15
- package/lib/runjs-context/snippets/scene/jsfield/format-number.snippet.d.ts +0 -15
- package/lib/runjs-context/snippets/scene/jsfield/innerHTML-value.snippet.d.ts +0 -15
- package/lib/runjs-context/snippets/scene/jsitem/render-basic.snippet.d.ts +0 -15
- package/lib/runjs-context/snippets/scene/linkage/set-disabled.snippet.d.ts +0 -15
- package/lib/runjs-context/snippets/scene/linkage/set-field-value.snippet.d.ts +0 -15
- package/lib/runjs-context/snippets/scene/linkage/set-required.snippet.d.ts +0 -15
- package/lib/runjs-context/snippets/scene/linkage/toggle-visible.snippet.d.ts +0 -15
- package/src/runjs-context/contexts/FlowRunJSContext.ts +0 -190
- package/src/runjs-context/contexts/LinkageRunJSContext.ts +0 -35
- package/src/runjs-context/index.ts +0 -20
- package/src/runjs-context/snippets/global/api-request-get.snippet.ts +0 -20
- package/src/runjs-context/snippets/global/api-request-post.snippet.ts +0 -20
- package/src/runjs-context/snippets/global/console-log-ctx.snippet.ts +0 -19
- package/src/runjs-context/snippets/global/copy-record-json.snippet.ts +0 -21
- package/src/runjs-context/snippets/global/copy-to-clipboard.snippet.ts +0 -21
- package/src/runjs-context/snippets/global/log-json-record.snippet.ts +0 -21
- package/src/runjs-context/snippets/global/requireAsync.snippet.ts +0 -24
- package/src/runjs-context/snippets/global/sleep.snippet.ts +0 -21
- package/src/runjs-context/snippets/global/try-catch-async.snippet.ts +0 -22
- package/src/runjs-context/snippets/global/view-navigation-push.snippet.ts +0 -23
- package/src/runjs-context/snippets/libs/echarts-init.snippet.ts +0 -24
- package/src/runjs-context/snippets/scene/actions/record-id-message.snippet.ts +0 -21
- package/src/runjs-context/snippets/scene/actions/run-action-basic.snippet.ts +0 -18
- package/src/runjs-context/snippets/scene/jsblock/append-style.snippet.ts +0 -20
- package/src/runjs-context/snippets/scene/jsblock/jsx-mount.snippet.ts +0 -24
- package/src/runjs-context/snippets/scene/jsblock/jsx-unmount.snippet.ts +0 -19
- package/src/runjs-context/snippets/scene/jsblock/render-basic.snippet.ts +0 -24
- package/src/runjs-context/snippets/scene/jsblock/render-button-handler.snippet.ts +0 -24
- package/src/runjs-context/snippets/scene/jsblock/render-card.snippet.ts +0 -30
- package/src/runjs-context/snippets/scene/jsblock/render-react.snippet.ts +0 -34
- package/src/runjs-context/snippets/scene/jsfield/color-by-value.snippet.ts +0 -20
- package/src/runjs-context/snippets/scene/jsfield/format-number.snippet.ts +0 -19
- package/src/runjs-context/snippets/scene/jsfield/innerHTML-value.snippet.ts +0 -18
- /package/lib/runjs-context/snippets/global/{copy-record-json.snippet.d.ts → api-request.snippet.d.ts} +0 -0
- /package/lib/runjs-context/snippets/global/{copy-to-clipboard.snippet.d.ts → clipboard-copy-text.snippet.d.ts} +0 -0
- /package/lib/runjs-context/snippets/global/{log-json-record.snippet.d.ts → import-esm.snippet.d.ts} +0 -0
- /package/lib/runjs-context/snippets/global/{view-navigation-push.snippet.d.ts → query-selector.snippet.d.ts} +0 -0
- /package/lib/runjs-context/snippets/scene/{jsblock/render-card.snippet.d.ts → block/echarts-init.snippet.d.ts} +0 -0
|
@@ -0,0 +1,45 @@
|
|
|
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 type { SnippetModule } from '../../types';
|
|
11
|
+
import { JSCollectionActionRunJSContext } from '../../../contexts/JSCollectionActionRunJSContext';
|
|
12
|
+
|
|
13
|
+
const snippet: SnippetModule = {
|
|
14
|
+
contexts: [JSCollectionActionRunJSContext],
|
|
15
|
+
prefix: 'sn-act-export',
|
|
16
|
+
label: 'Export selected rows as JSON',
|
|
17
|
+
description: 'Download selected rows as a JSON file',
|
|
18
|
+
locales: {
|
|
19
|
+
'zh-CN': {
|
|
20
|
+
label: '导出选中行为 JSON',
|
|
21
|
+
description: '将选中的行导出为 JSON 文件下载',
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
content: `
|
|
25
|
+
const rows = ctx.resource?.getSelectedRows?.() || [];
|
|
26
|
+
if (!rows.length) {
|
|
27
|
+
ctx.message.warning(ctx.t('Please select at least one row'));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Create JSON file and download
|
|
32
|
+
const jsonStr = JSON.stringify(rows, null, 2);
|
|
33
|
+
const blob = new Blob([jsonStr], { type: 'application/json' });
|
|
34
|
+
const url = URL.createObjectURL(blob);
|
|
35
|
+
const link = document.createElement('a');
|
|
36
|
+
link.href = url;
|
|
37
|
+
link.download = \`export-\${new Date().toISOString().split('T')[0]}.json\`;
|
|
38
|
+
link.click();
|
|
39
|
+
URL.revokeObjectURL(url);
|
|
40
|
+
|
|
41
|
+
ctx.message.success(ctx.t('Exported {{count}} records', { count: rows.length }));
|
|
42
|
+
`,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export default snippet;
|
|
@@ -7,10 +7,20 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
import type { SnippetModule } from '../../types';
|
|
11
|
+
import { JSCollectionActionRunJSContext } from '../../../contexts/JSCollectionActionRunJSContext';
|
|
12
|
+
|
|
13
|
+
const snippet: SnippetModule = {
|
|
14
|
+
contexts: [JSCollectionActionRunJSContext],
|
|
12
15
|
prefix: 'sn-act-iterate',
|
|
13
16
|
label: 'Iterate selected rows',
|
|
17
|
+
description: 'Loop through selected rows and process each record',
|
|
18
|
+
locales: {
|
|
19
|
+
'zh-CN': {
|
|
20
|
+
label: '遍历选中行',
|
|
21
|
+
description: '遍历选中行并处理每条记录',
|
|
22
|
+
},
|
|
23
|
+
},
|
|
14
24
|
content: `
|
|
15
25
|
const rows = ctx.resource?.getSelectedRows?.() || [];
|
|
16
26
|
for (const row of rows) {
|
|
@@ -19,3 +29,5 @@ for (const row of rows) {
|
|
|
19
29
|
ctx.message.success(ctx.t('Processed {{count}} rows', { count: rows.length }));
|
|
20
30
|
`,
|
|
21
31
|
};
|
|
32
|
+
|
|
33
|
+
export default snippet;
|
|
@@ -7,11 +7,15 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
export type SnippetContextSpecifier = string | { name: string } | (new (...args: any[]) => any);
|
|
11
|
+
|
|
10
12
|
export type SnippetModule = {
|
|
11
13
|
content: string;
|
|
12
|
-
contexts?:
|
|
14
|
+
contexts?: SnippetContextSpecifier[]; // e.g., [JSBlockRunJSContext] or ['*']
|
|
13
15
|
versions?: string[]; // e.g., ['v1'] or ['*']
|
|
16
|
+
scenes?: string[]; // logical placement hints like ['form', 'table']
|
|
14
17
|
prefix?: string; // completion trigger text
|
|
15
18
|
label?: string; // display name in snippets list
|
|
16
19
|
description?: string; // optional longer description
|
|
20
|
+
locales?: Record<string, { label?: string; description?: string }>;
|
|
17
21
|
};
|
|
@@ -0,0 +1,304 @@
|
|
|
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 { uid as genUid } from 'uid/secure';
|
|
11
|
+
import type { FlowEngine } from '../flowEngine';
|
|
12
|
+
import type { FlowModel } from '../models/flowModel';
|
|
13
|
+
|
|
14
|
+
type LifecycleType =
|
|
15
|
+
| 'created'
|
|
16
|
+
| 'mounted'
|
|
17
|
+
| 'unmounted'
|
|
18
|
+
| 'destroyed'
|
|
19
|
+
| `event:${string}:start`
|
|
20
|
+
| `event:${string}:end`
|
|
21
|
+
| `event:${string}:error`;
|
|
22
|
+
|
|
23
|
+
export type ScheduleWhen = LifecycleType | ((e: LifecycleEvent) => boolean);
|
|
24
|
+
|
|
25
|
+
export interface ScheduleOptions {
|
|
26
|
+
when?: ScheduleWhen;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type ScheduledCancel = () => boolean;
|
|
30
|
+
|
|
31
|
+
export interface LifecycleEvent {
|
|
32
|
+
type: LifecycleType;
|
|
33
|
+
uid: string;
|
|
34
|
+
model?: FlowModel;
|
|
35
|
+
runId?: string;
|
|
36
|
+
error?: any;
|
|
37
|
+
inputArgs?: Record<string, any>;
|
|
38
|
+
result?: any;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
type ScheduledItem = {
|
|
42
|
+
id: string;
|
|
43
|
+
fromUid: string;
|
|
44
|
+
toUid: string;
|
|
45
|
+
fn: (model: FlowModel) => Promise<void> | void;
|
|
46
|
+
options: ScheduleOptions;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export class ModelOperationScheduler {
|
|
50
|
+
private itemsByTargetUid = new Map<string, Map<string, ScheduledItem>>();
|
|
51
|
+
private itemIdsByFromUid = new Map<string, Set<string>>();
|
|
52
|
+
private itemsById = new Map<string, ScheduledItem>();
|
|
53
|
+
|
|
54
|
+
private unbindHandlers: Array<() => void> = [];
|
|
55
|
+
private subscribedEventNames = new Set<string>();
|
|
56
|
+
|
|
57
|
+
constructor(private engine: FlowEngine) {
|
|
58
|
+
this.bindEngineLifecycle();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** 外部调用入口 */
|
|
62
|
+
schedule(
|
|
63
|
+
fromModelOrUid: FlowModel | string,
|
|
64
|
+
toUid: string,
|
|
65
|
+
fn: (model: FlowModel) => Promise<void> | void,
|
|
66
|
+
options?: ScheduleOptions,
|
|
67
|
+
): ScheduledCancel {
|
|
68
|
+
const fromUid = typeof fromModelOrUid === 'string' ? fromModelOrUid : fromModelOrUid?.uid;
|
|
69
|
+
if (!fromUid) {
|
|
70
|
+
throw new Error('scheduleModelOperation: invalid from model or uid');
|
|
71
|
+
}
|
|
72
|
+
if (!toUid || typeof toUid !== 'string') {
|
|
73
|
+
throw new Error('scheduleModelOperation: invalid target uid');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const item: ScheduledItem = {
|
|
77
|
+
id: genUid(),
|
|
78
|
+
fromUid,
|
|
79
|
+
toUid,
|
|
80
|
+
fn,
|
|
81
|
+
options: {
|
|
82
|
+
// 默认语义:仅当 when === 'created' 时,对“已存在的目标”进行一次立即执行;
|
|
83
|
+
// 其它 when 不做立即执行,需等待后续真实生命周期事件。
|
|
84
|
+
when: options?.when ?? 'created',
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// 注册结构
|
|
89
|
+
let targetMap = this.itemsByTargetUid.get(toUid);
|
|
90
|
+
if (!targetMap) {
|
|
91
|
+
targetMap = new Map();
|
|
92
|
+
this.itemsByTargetUid.set(toUid, targetMap);
|
|
93
|
+
}
|
|
94
|
+
targetMap.set(item.id, item);
|
|
95
|
+
let fromSet = this.itemIdsByFromUid.get(fromUid);
|
|
96
|
+
if (!fromSet) {
|
|
97
|
+
fromSet = new Set();
|
|
98
|
+
this.itemIdsByFromUid.set(fromUid, fromSet);
|
|
99
|
+
}
|
|
100
|
+
fromSet.add(item.id);
|
|
101
|
+
this.itemsById.set(item.id, item);
|
|
102
|
+
|
|
103
|
+
// 若是 event:xxx:start/end 语义,确保已订阅对应的 model:event 通道
|
|
104
|
+
this.ensureEventSubscriptionIfNeeded(item.options.when);
|
|
105
|
+
|
|
106
|
+
const modelNow = this.engine.getModel<FlowModel>(toUid);
|
|
107
|
+
if (modelNow && item.options.when === 'created') {
|
|
108
|
+
// 仅 when === 'created' 时,对“已存在的目标”立即执行一次
|
|
109
|
+
void this.tryExecuteOnce(item.id, { type: 'created', uid: toUid, model: modelNow });
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return () => this.internalCancel(item.id);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
cancel(filter: { fromUid?: string; toUid?: string }) {
|
|
116
|
+
const { fromUid, toUid } = filter || {};
|
|
117
|
+
if (toUid && this.itemsByTargetUid.has(toUid)) {
|
|
118
|
+
const map = this.itemsByTargetUid.get(toUid);
|
|
119
|
+
if (map)
|
|
120
|
+
for (const item of map.values()) {
|
|
121
|
+
if (fromUid && item.fromUid !== fromUid) continue;
|
|
122
|
+
this.internalCancel(item.id);
|
|
123
|
+
}
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (fromUid && this.itemIdsByFromUid.has(fromUid)) {
|
|
127
|
+
const set = this.itemIdsByFromUid.get(fromUid);
|
|
128
|
+
if (set)
|
|
129
|
+
for (const id of Array.from(set)) {
|
|
130
|
+
const it = this.itemsById.get(id);
|
|
131
|
+
if (!it) continue;
|
|
132
|
+
if (toUid && it.toUid !== toUid) continue;
|
|
133
|
+
this.internalCancel(id);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** 引擎关闭时释放 */
|
|
139
|
+
dispose() {
|
|
140
|
+
try {
|
|
141
|
+
for (const unbind of this.unbindHandlers) {
|
|
142
|
+
try {
|
|
143
|
+
unbind();
|
|
144
|
+
} catch (err) {
|
|
145
|
+
// 忽略解绑异常
|
|
146
|
+
void err;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
} finally {
|
|
150
|
+
this.unbindHandlers = [];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 统一取消剩余任务(将触发 cleanupItem,从而移除 abort 监听与计时器)
|
|
154
|
+
for (const map of this.itemsByTargetUid.values()) {
|
|
155
|
+
for (const item of map.values()) this.internalCancel(item.id);
|
|
156
|
+
}
|
|
157
|
+
this.itemsByTargetUid.clear();
|
|
158
|
+
this.itemIdsByFromUid.clear();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private bindEngineLifecycle() {
|
|
162
|
+
const emitter = this.engine.emitter;
|
|
163
|
+
if (!emitter || typeof emitter.on !== 'function') return;
|
|
164
|
+
|
|
165
|
+
const onCreated = (e: LifecycleEvent) => {
|
|
166
|
+
this.processLifecycleEvent(e.uid, { ...e, type: 'created' });
|
|
167
|
+
};
|
|
168
|
+
emitter.on('model:created', onCreated);
|
|
169
|
+
this.unbindHandlers.push(() => emitter.off('model:created', onCreated));
|
|
170
|
+
|
|
171
|
+
const onMounted = (e: LifecycleEvent) => {
|
|
172
|
+
this.processLifecycleEvent(e.uid, { ...e, type: 'mounted' });
|
|
173
|
+
};
|
|
174
|
+
emitter.on('model:mounted', onMounted);
|
|
175
|
+
this.unbindHandlers.push(() => emitter.off('model:mounted', onMounted));
|
|
176
|
+
|
|
177
|
+
const onGenericBeforeStart = (e: LifecycleEvent) => {
|
|
178
|
+
this.processLifecycleEvent(e.uid, { ...e, type: 'event:beforeRender:start' });
|
|
179
|
+
};
|
|
180
|
+
emitter.on('model:event:beforeRender:start', onGenericBeforeStart);
|
|
181
|
+
this.unbindHandlers.push(() => emitter.off('model:event:beforeRender:start', onGenericBeforeStart));
|
|
182
|
+
|
|
183
|
+
const onGenericBeforeEnd = (e: LifecycleEvent) => {
|
|
184
|
+
this.processLifecycleEvent(e.uid, { ...e, type: 'event:beforeRender:end' });
|
|
185
|
+
};
|
|
186
|
+
emitter.on('model:event:beforeRender:end', onGenericBeforeEnd);
|
|
187
|
+
this.unbindHandlers.push(() => emitter.off('model:event:beforeRender:end', onGenericBeforeEnd));
|
|
188
|
+
|
|
189
|
+
const onUnmounted = (e: LifecycleEvent) => {
|
|
190
|
+
this.processLifecycleEvent(e.uid, { ...e, type: 'unmounted' });
|
|
191
|
+
};
|
|
192
|
+
emitter.on('model:unmounted', onUnmounted);
|
|
193
|
+
this.unbindHandlers.push(() => emitter.off('model:unmounted', onUnmounted));
|
|
194
|
+
|
|
195
|
+
const onDestroyed = (e: LifecycleEvent) => {
|
|
196
|
+
const targetBucket = this.itemsByTargetUid.get(e.uid);
|
|
197
|
+
const event = { ...e, type: 'destroyed' as const };
|
|
198
|
+
if (targetBucket && targetBucket.size) {
|
|
199
|
+
const ids = Array.from(targetBucket.keys());
|
|
200
|
+
for (const id of ids) {
|
|
201
|
+
const it = this.itemsById.get(id);
|
|
202
|
+
if (!it) continue;
|
|
203
|
+
if (this.shouldTrigger(it.options.when, event)) {
|
|
204
|
+
void this.tryExecuteOnce(id, event);
|
|
205
|
+
} else {
|
|
206
|
+
this.internalCancel(id);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
emitter.on('model:destroyed', onDestroyed);
|
|
212
|
+
this.unbindHandlers.push(() => emitter.off('model:destroyed', onDestroyed));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
private ensureEventSubscriptionIfNeeded(when?: ScheduleWhen) {
|
|
216
|
+
if (!when || typeof when !== 'string') return;
|
|
217
|
+
const parsed = this.parseEventWhen(when);
|
|
218
|
+
if (!parsed) return;
|
|
219
|
+
const { name } = parsed;
|
|
220
|
+
if (this.subscribedEventNames.has(name)) return;
|
|
221
|
+
this.subscribedEventNames.add(name);
|
|
222
|
+
const emitter = this.engine.emitter;
|
|
223
|
+
const onStart = (e: LifecycleEvent) => {
|
|
224
|
+
this.processLifecycleEvent(e.uid, { ...e, type: `event:${name}:start` as const });
|
|
225
|
+
};
|
|
226
|
+
const onEnd = (e: LifecycleEvent) => {
|
|
227
|
+
this.processLifecycleEvent(e.uid, { ...e, type: `event:${name}:end` as const });
|
|
228
|
+
};
|
|
229
|
+
const onError = (e: LifecycleEvent) => {
|
|
230
|
+
this.processLifecycleEvent(e.uid, { ...e, type: `event:${name}:error` as const });
|
|
231
|
+
};
|
|
232
|
+
emitter.on(`model:event:${name}:start`, onStart);
|
|
233
|
+
emitter.on(`model:event:${name}:end`, onEnd);
|
|
234
|
+
emitter.on(`model:event:${name}:error`, onError);
|
|
235
|
+
this.unbindHandlers.push(() => emitter.off(`model:event:${name}:start`, onStart));
|
|
236
|
+
this.unbindHandlers.push(() => emitter.off(`model:event:${name}:end`, onEnd));
|
|
237
|
+
this.unbindHandlers.push(() => emitter.off(`model:event:${name}:error`, onError));
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
private parseEventWhen(when?: ScheduleWhen): { name: string; phase: 'start' | 'end' | 'error' } | null {
|
|
241
|
+
if (!when || typeof when !== 'string') return null;
|
|
242
|
+
const m = /^event:([^:]+):(start|end|error)$/.exec(when);
|
|
243
|
+
if (!m) return null;
|
|
244
|
+
return { name: m[1], phase: m[2] as 'start' | 'end' | 'error' };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private processLifecycleEvent(targetUid: string, event: LifecycleEvent) {
|
|
248
|
+
const targetBucket = this.itemsByTargetUid.get(targetUid);
|
|
249
|
+
if (!targetBucket || targetBucket.size === 0) return;
|
|
250
|
+
const ids = Array.from(targetBucket.keys());
|
|
251
|
+
for (const id of ids) {
|
|
252
|
+
const item = this.itemsById.get(id);
|
|
253
|
+
if (!item) continue;
|
|
254
|
+
const should = this.shouldTrigger(item.options.when, event);
|
|
255
|
+
if (!should) continue;
|
|
256
|
+
void this.tryExecuteOnce(id, event);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private shouldTrigger(when: ScheduleWhen | undefined, event: LifecycleEvent): boolean {
|
|
261
|
+
if (!when) return false;
|
|
262
|
+
if (typeof when === 'function') return !!when(event);
|
|
263
|
+
return when === event.type;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
private async tryExecuteOnce(id: string, event: LifecycleEvent) {
|
|
267
|
+
const item = this.takeItem(id);
|
|
268
|
+
if (!item) return;
|
|
269
|
+
try {
|
|
270
|
+
const model = event.model || this.engine.getModel<FlowModel>(item.toUid);
|
|
271
|
+
if (!model) return;
|
|
272
|
+
await Promise.resolve(item.fn(model));
|
|
273
|
+
} catch (err) {
|
|
274
|
+
this.engine.logger?.error?.(
|
|
275
|
+
{ err, id, fromUid: item.fromUid, toUid: item.toUid, when: item.options.when },
|
|
276
|
+
'ModelOperationScheduler: operation execution failed',
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
private internalCancel(id: string) {
|
|
282
|
+
return !!this.takeItem(id);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
private takeItem(id: string): ScheduledItem | undefined {
|
|
286
|
+
const it = this.itemsById.get(id);
|
|
287
|
+
if (!it) return undefined;
|
|
288
|
+
const { toUid, fromUid } = it;
|
|
289
|
+
const map = this.itemsByTargetUid.get(toUid);
|
|
290
|
+
if (map) {
|
|
291
|
+
map.delete(id);
|
|
292
|
+
if (map.size === 0) this.itemsByTargetUid.delete(toUid);
|
|
293
|
+
}
|
|
294
|
+
const set = this.itemIdsByFromUid.get(fromUid);
|
|
295
|
+
if (set) {
|
|
296
|
+
set.delete(id);
|
|
297
|
+
if (set.size === 0) this.itemIdsByFromUid.delete(fromUid);
|
|
298
|
+
}
|
|
299
|
+
this.itemsById.delete(id);
|
|
300
|
+
return it;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export default ModelOperationScheduler;
|
package/src/types.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { ISchema } from '@formily/json-schema';
|
|
11
11
|
import { SubModelItem } from './components';
|
|
12
12
|
import { FlowContext, FlowModelContext, FlowRuntimeContext, FlowSettingsContext } from './flowContext';
|
|
13
|
+
import type { PropertyOptions } from './flowContext';
|
|
13
14
|
import type { FlowEngine } from './flowEngine';
|
|
14
15
|
import type { FlowModel } from './models';
|
|
15
16
|
import { FilterGroupOptions } from './resources';
|
|
@@ -134,6 +135,8 @@ export enum ActionScene {
|
|
|
134
135
|
BLOCK_LINKAGE_RULES = 1,
|
|
135
136
|
/** 表单字段级联动规则可用 */
|
|
136
137
|
FIELD_LINKAGE_RULES,
|
|
138
|
+
/** 子表单字段级联动规则可用 */
|
|
139
|
+
SUB_FORM_FIELD_LINKAGE_RULES,
|
|
137
140
|
/** 详情字段级联动规则可用 */
|
|
138
141
|
DETAILS_FIELD_LINKAGE_RULES,
|
|
139
142
|
/** 按钮级联动规则可用 */
|
|
@@ -157,6 +160,26 @@ export interface ActionDefinition<TModel extends FlowModel = FlowModel, TCtx ext
|
|
|
157
160
|
uiMode?: StepUIMode | ((ctx: FlowRuntimeContext<TModel>) => StepUIMode | Promise<StepUIMode>);
|
|
158
161
|
scene?: ActionScene | ActionScene[];
|
|
159
162
|
sort?: number;
|
|
163
|
+
/**
|
|
164
|
+
* 在执行 Action 前为 ctx 定义临时属性。
|
|
165
|
+
* - 仅支持 PropertyOptions 形态(例如:{ foo: { value: 5 } });
|
|
166
|
+
* - 或函数形式(接收 ctx,返回 PropertyOptions 对象;支持 Promise)。
|
|
167
|
+
*/
|
|
168
|
+
defineProperties?:
|
|
169
|
+
| Record<string, PropertyOptions>
|
|
170
|
+
| ((ctx: TCtx) => Record<string, PropertyOptions> | Promise<Record<string, PropertyOptions>>);
|
|
171
|
+
/**
|
|
172
|
+
* 在执行 Action 前为 ctx 定义临时方法。
|
|
173
|
+
* - 支持 JSON 形式(对象的值应为函数)
|
|
174
|
+
* - 或函数形式(接收 ctx,返回方法对象;支持 Promise)
|
|
175
|
+
*/
|
|
176
|
+
defineMethods?:
|
|
177
|
+
| Record<string, (this: TCtx, ...args: any[]) => any>
|
|
178
|
+
| ((
|
|
179
|
+
ctx: TCtx,
|
|
180
|
+
) =>
|
|
181
|
+
| Record<string, (this: TCtx, ...args: any[]) => any>
|
|
182
|
+
| Promise<Record<string, (this: TCtx, ...args: any[]) => any>>);
|
|
160
183
|
}
|
|
161
184
|
|
|
162
185
|
/**
|
|
@@ -185,6 +208,16 @@ export type FlowEvent<TModel extends FlowModel = FlowModel> =
|
|
|
185
208
|
| FlowEventName
|
|
186
209
|
| { eventName: FlowEventName; defaultParams?: Record<string, any> };
|
|
187
210
|
|
|
211
|
+
/**
|
|
212
|
+
* 事件分发选项。
|
|
213
|
+
*/
|
|
214
|
+
export interface DispatchEventOptions {
|
|
215
|
+
/** 是否顺序执行(默认并行) */
|
|
216
|
+
sequential?: boolean;
|
|
217
|
+
/** 是否使用缓存(默认 false;beforeRender 的默认值为 true,可覆盖) */
|
|
218
|
+
useCache?: boolean;
|
|
219
|
+
}
|
|
220
|
+
|
|
188
221
|
/**
|
|
189
222
|
* 事件定义:用于事件注册表(全局/模型类级)。
|
|
190
223
|
*/
|
|
@@ -300,6 +333,7 @@ export interface IFlowModelRepository<T extends FlowModel = FlowModel> {
|
|
|
300
333
|
save(model: T, options?: { onlyStepParams?: boolean }): Promise<Record<string, any>>;
|
|
301
334
|
destroy(uid: string): Promise<boolean>;
|
|
302
335
|
move(sourceId: string, targetId: string, position: 'before' | 'after'): Promise<void>;
|
|
336
|
+
duplicate(uid: string): Promise<Record<string, any> | null>;
|
|
303
337
|
}
|
|
304
338
|
|
|
305
339
|
/**
|
|
@@ -0,0 +1,65 @@
|
|
|
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, test, expect } from 'vitest';
|
|
11
|
+
import { FlowEngine } from '../../flowEngine';
|
|
12
|
+
import { FlowModel } from '../../models/flowModel';
|
|
13
|
+
import { isBeforeRenderFlow } from '../index';
|
|
14
|
+
|
|
15
|
+
describe('utils/isBeforeRenderFlow', () => {
|
|
16
|
+
test('identifies beforeRender by on string', () => {
|
|
17
|
+
const engine = new FlowEngine();
|
|
18
|
+
class M extends FlowModel {}
|
|
19
|
+
engine.registerModels({ M });
|
|
20
|
+
const m = engine.createModel({ use: 'M' });
|
|
21
|
+
m.registerFlow('A', { on: 'beforeRender', steps: {} });
|
|
22
|
+
const flow = m.flowRegistry.getFlow('A')!;
|
|
23
|
+
expect(isBeforeRenderFlow(flow)).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('identifies beforeRender by on object', () => {
|
|
27
|
+
const engine = new FlowEngine();
|
|
28
|
+
class M extends FlowModel {}
|
|
29
|
+
engine.registerModels({ M });
|
|
30
|
+
const m = engine.createModel({ use: 'M' });
|
|
31
|
+
m.registerFlow('B', { on: { eventName: 'beforeRender' }, steps: {} });
|
|
32
|
+
const flow = m.flowRegistry.getFlow('B')!;
|
|
33
|
+
expect(isBeforeRenderFlow(flow)).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('treats missing on and non-manual as beforeRender', () => {
|
|
37
|
+
const engine = new FlowEngine();
|
|
38
|
+
class M extends FlowModel {}
|
|
39
|
+
engine.registerModels({ M });
|
|
40
|
+
const m = engine.createModel({ use: 'M' });
|
|
41
|
+
m.registerFlow('C', { steps: {} });
|
|
42
|
+
const flow = m.flowRegistry.getFlow('C')!;
|
|
43
|
+
expect(isBeforeRenderFlow(flow)).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('manual flow is not beforeRender when on is missing', () => {
|
|
47
|
+
const engine = new FlowEngine();
|
|
48
|
+
class M extends FlowModel {}
|
|
49
|
+
engine.registerModels({ M });
|
|
50
|
+
const m = engine.createModel({ use: 'M' });
|
|
51
|
+
m.registerFlow('D', { manual: true, steps: {} });
|
|
52
|
+
const flow = m.flowRegistry.getFlow('D')!;
|
|
53
|
+
expect(isBeforeRenderFlow(flow)).toBe(false);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('non-beforeRender event is not beforeRender', () => {
|
|
57
|
+
const engine = new FlowEngine();
|
|
58
|
+
class M extends FlowModel {}
|
|
59
|
+
engine.registerModels({ M });
|
|
60
|
+
const m = engine.createModel({ use: 'M' });
|
|
61
|
+
m.registerFlow('E', { on: { eventName: 'click' }, steps: {} });
|
|
62
|
+
const flow = m.flowRegistry.getFlow('E')!;
|
|
63
|
+
expect(isBeforeRenderFlow(flow)).toBe(false);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
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, it, expect } from 'vitest';
|
|
11
|
+
import { compileRunJs } from '../../utils/jsxTransform';
|
|
12
|
+
|
|
13
|
+
describe('compileRunJs', () => {
|
|
14
|
+
it('returns original code when no JSX is present', async () => {
|
|
15
|
+
const src = `const a = 1; const b = a + 1;`;
|
|
16
|
+
const out = await compileRunJs(src);
|
|
17
|
+
expect(out).toBe(src);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('transforms JSX when sucrase is available (skip if missing)', async () => {
|
|
21
|
+
const src = `ctx.render(<div className="x">hi</div>);`;
|
|
22
|
+
|
|
23
|
+
// Try to import sucrase to decide if this environment has it installed
|
|
24
|
+
const hasSucrase = await import('sucrase').then(() => true).catch(() => false);
|
|
25
|
+
|
|
26
|
+
const out = await compileRunJs(src);
|
|
27
|
+
|
|
28
|
+
if (!hasSucrase) {
|
|
29
|
+
// Environment without sucrase: current implementation falls back to original code
|
|
30
|
+
expect(out).toBe(src);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// sucrase available: output should contain React.createElement mapping
|
|
35
|
+
expect(out).not.toBe(src);
|
|
36
|
+
expect(out).toMatch(/ctx\.React\.createElement/);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -101,4 +101,29 @@ describe('parsePathnameToViewParams', () => {
|
|
|
101
101
|
const result = parsePathnameToViewParams('///admin//xxx//tab//yyy//');
|
|
102
102
|
expect(result).toEqual([{ viewUid: 'xxx', tabUid: 'yyy' }]);
|
|
103
103
|
});
|
|
104
|
+
|
|
105
|
+
test('should parse filterByTk from key-value encoded segment into object', () => {
|
|
106
|
+
const kv = encodeURIComponent('id=1&tenant=ac');
|
|
107
|
+
const path = `/admin/xxx/filterbytk/${kv}`;
|
|
108
|
+
const result = parsePathnameToViewParams(path);
|
|
109
|
+
expect(result).toEqual([{ viewUid: 'xxx', filterByTk: { id: '1', tenant: 'ac' } }]);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('should parse filterByTk from JSON object segment', () => {
|
|
113
|
+
const json = encodeURIComponent('{"id":"1","tenant":"ac"}');
|
|
114
|
+
const path = `/admin/xxx/filterbytk/${json}`;
|
|
115
|
+
const result = parsePathnameToViewParams(path);
|
|
116
|
+
expect(result).toEqual([{ viewUid: 'xxx', filterByTk: { id: '1', tenant: 'ac' } }]);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test('should keep non-object JSON (array/number) as string for filterByTk', () => {
|
|
120
|
+
const arr = encodeURIComponent('["a"]');
|
|
121
|
+
const num = encodeURIComponent('123');
|
|
122
|
+
const t = encodeURIComponent('true');
|
|
123
|
+
expect(parsePathnameToViewParams(`/admin/xxx/filterbytk/${arr}`)).toEqual([
|
|
124
|
+
{ viewUid: 'xxx', filterByTk: '["a"]' },
|
|
125
|
+
]);
|
|
126
|
+
expect(parsePathnameToViewParams(`/admin/xxx/filterbytk/${num}`)).toEqual([{ viewUid: 'xxx', filterByTk: '123' }]);
|
|
127
|
+
expect(parsePathnameToViewParams(`/admin/xxx/filterbytk/${t}`)).toEqual([{ viewUid: 'xxx', filterByTk: 'true' }]);
|
|
128
|
+
});
|
|
104
129
|
});
|
|
@@ -0,0 +1,38 @@
|
|
|
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, it, expect } from 'vitest';
|
|
11
|
+
import { pruneFilter } from '../pruneFilter';
|
|
12
|
+
|
|
13
|
+
describe('pruneFilter', () => {
|
|
14
|
+
it('keeps boolean false and number 0', () => {
|
|
15
|
+
const input = { a: { $eq: false }, b: { $eq: 0 } };
|
|
16
|
+
const out = pruneFilter(input);
|
|
17
|
+
expect(out).toEqual(input);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('removes null/undefined/empty string and empty containers', () => {
|
|
21
|
+
const input = { a: null, b: undefined, c: '', d: [], e: {} } as any;
|
|
22
|
+
const out = pruneFilter(input);
|
|
23
|
+
expect(out).toBeUndefined();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('works recursively with nested objects/arrays', () => {
|
|
27
|
+
const input = {
|
|
28
|
+
$and: [{ isRead: { $eq: false } }, { name: { $eq: '' } }, {}, [], { nested: { empty: {}, ok: 1 } }],
|
|
29
|
+
} as any;
|
|
30
|
+
const out = pruneFilter(input);
|
|
31
|
+
expect(out).toEqual({ $and: [{ isRead: { $eq: false } }, { nested: { ok: 1 } }] });
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('returns undefined for empty arrays/objects produced by pruning', () => {
|
|
35
|
+
expect(pruneFilter([null, undefined, '', [], {}] as any)).toBeUndefined();
|
|
36
|
+
expect(pruneFilter({ a: { b: '' } } as any)).toBeUndefined();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { describe, expect, it } from 'vitest';
|
|
11
|
-
import { createSafeDocument, createSafeWindow } from '../safeGlobals';
|
|
11
|
+
import { createSafeDocument, createSafeWindow, createSafeNavigator } from '../safeGlobals';
|
|
12
12
|
|
|
13
13
|
describe('safeGlobals', () => {
|
|
14
14
|
it('createSafeWindow exposes only allowed globals and extras', () => {
|
|
@@ -26,4 +26,25 @@ describe('safeGlobals', () => {
|
|
|
26
26
|
expect(doc.bar).toBe(true);
|
|
27
27
|
expect(() => doc.cookie).toThrow(/not allowed/);
|
|
28
28
|
});
|
|
29
|
+
|
|
30
|
+
it('createSafeNavigator exposes limited props and guards others', () => {
|
|
31
|
+
const nav: any = createSafeNavigator();
|
|
32
|
+
// clipboard object should always exist
|
|
33
|
+
expect(typeof nav.clipboard).toBe('object');
|
|
34
|
+
// writeText may or may not exist depending on environment
|
|
35
|
+
if (typeof navigator !== 'undefined' && (navigator as any).clipboard?.writeText) {
|
|
36
|
+
expect(typeof nav.clipboard.writeText).toBe('function');
|
|
37
|
+
} else {
|
|
38
|
+
expect(typeof nav.clipboard.writeText === 'undefined' || typeof nav.clipboard.writeText === 'function').toBe(
|
|
39
|
+
true,
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
// readable properties
|
|
43
|
+
expect(() => void nav.onLine).not.toThrow();
|
|
44
|
+
expect(() => void nav.language).not.toThrow();
|
|
45
|
+
expect(() => void nav.languages).not.toThrow();
|
|
46
|
+
// blocked properties
|
|
47
|
+
expect(() => (nav as any).geolocation).toThrow(/not allowed/);
|
|
48
|
+
expect(() => (nav as any).userAgent).toThrow(/not allowed/);
|
|
49
|
+
});
|
|
29
50
|
});
|