@nocobase/flow-engine 2.1.0-alpha.1 → 2.1.0-alpha.11

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 (312) hide show
  1. package/LICENSE +201 -661
  2. package/README.md +79 -10
  3. package/lib/BlockScopedFlowEngine.js +0 -1
  4. package/lib/FlowDefinition.d.ts +6 -0
  5. package/lib/FlowSchemaRegistry.d.ts +154 -0
  6. package/lib/FlowSchemaRegistry.js +1427 -0
  7. package/lib/JSRunner.d.ts +15 -0
  8. package/lib/JSRunner.js +82 -7
  9. package/lib/ViewScopedFlowEngine.js +8 -1
  10. package/lib/acl/Acl.js +13 -3
  11. package/lib/components/FlowContextSelector.js +155 -10
  12. package/lib/components/MobilePopup.js +6 -5
  13. package/lib/components/dnd/gridDragPlanner.d.ts +1 -0
  14. package/lib/components/dnd/gridDragPlanner.js +59 -3
  15. package/lib/components/settings/wrappers/component/SwitchWithTitle.js +2 -1
  16. package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.js +76 -15
  17. package/lib/components/settings/wrappers/contextual/FlowsContextMenu.js +24 -4
  18. package/lib/components/settings/wrappers/contextual/StepSettingsDialog.js +21 -3
  19. package/lib/components/subModel/AddSubModelButton.js +27 -1
  20. package/lib/components/subModel/utils.js +2 -2
  21. package/lib/components/variables/VariableInput.js +9 -4
  22. package/lib/components/variables/VariableTag.js +46 -39
  23. package/lib/components/variables/utils.d.ts +7 -0
  24. package/lib/components/variables/utils.js +42 -2
  25. package/lib/data-source/index.d.ts +7 -27
  26. package/lib/data-source/index.js +84 -51
  27. package/lib/executor/FlowExecutor.d.ts +2 -1
  28. package/lib/executor/FlowExecutor.js +190 -26
  29. package/lib/flow-schema-registry/fieldBinding.d.ts +32 -0
  30. package/lib/flow-schema-registry/fieldBinding.js +165 -0
  31. package/lib/flow-schema-registry/modelPatches.d.ts +16 -0
  32. package/lib/flow-schema-registry/modelPatches.js +235 -0
  33. package/lib/flow-schema-registry/schemaInference.d.ts +17 -0
  34. package/lib/flow-schema-registry/schemaInference.js +207 -0
  35. package/lib/flow-schema-registry/utils.d.ts +25 -0
  36. package/lib/flow-schema-registry/utils.js +293 -0
  37. package/lib/flowContext.d.ts +230 -7
  38. package/lib/flowContext.js +2270 -148
  39. package/lib/flowEngine.d.ts +160 -1
  40. package/lib/flowEngine.js +387 -27
  41. package/lib/flowI18n.js +6 -4
  42. package/lib/flowSettings.d.ts +14 -6
  43. package/lib/flowSettings.js +51 -17
  44. package/lib/index.d.ts +8 -1
  45. package/lib/index.js +24 -1
  46. package/lib/lazy-helper.d.ts +14 -0
  47. package/lib/lazy-helper.js +71 -0
  48. package/lib/locale/en-US.json +9 -2
  49. package/lib/locale/index.d.ts +14 -0
  50. package/lib/locale/zh-CN.json +8 -1
  51. package/lib/models/CollectionFieldModel.d.ts +1 -0
  52. package/lib/models/CollectionFieldModel.js +3 -2
  53. package/lib/models/DisplayItemModel.d.ts +1 -1
  54. package/lib/models/EditableItemModel.d.ts +1 -1
  55. package/lib/models/FilterableItemModel.d.ts +1 -1
  56. package/lib/models/flowModel.d.ts +7 -0
  57. package/lib/models/flowModel.js +83 -8
  58. package/lib/provider.js +7 -6
  59. package/lib/resources/baseRecordResource.d.ts +5 -0
  60. package/lib/resources/baseRecordResource.js +24 -0
  61. package/lib/resources/multiRecordResource.d.ts +1 -0
  62. package/lib/resources/multiRecordResource.js +11 -4
  63. package/lib/resources/singleRecordResource.js +2 -0
  64. package/lib/resources/sqlResource.d.ts +4 -3
  65. package/lib/resources/sqlResource.js +8 -3
  66. package/lib/runjs-context/contexts/FormJSFieldItemRunJSContext.js +12 -2
  67. package/lib/runjs-context/contexts/JSBlockRunJSContext.js +2 -2
  68. package/lib/runjs-context/contexts/JSEditableFieldRunJSContext.d.ts +16 -0
  69. package/lib/runjs-context/contexts/JSEditableFieldRunJSContext.js +125 -0
  70. package/lib/runjs-context/contexts/JSItemRunJSContext.js +12 -2
  71. package/lib/runjs-context/contexts/base.js +706 -41
  72. package/lib/runjs-context/contributions.d.ts +33 -0
  73. package/lib/runjs-context/contributions.js +88 -0
  74. package/lib/runjs-context/helpers.js +12 -1
  75. package/lib/runjs-context/registry.d.ts +1 -1
  76. package/lib/runjs-context/setup.js +23 -9
  77. package/lib/runjs-context/snippets/global/api-request.snippet.js +3 -3
  78. package/lib/runjs-context/snippets/global/import-esm.snippet.js +2 -3
  79. package/lib/runjs-context/snippets/global/query-selector.snippet.js +8 -3
  80. package/lib/runjs-context/snippets/global/require-amd.snippet.js +1 -1
  81. package/lib/runjs-context/snippets/index.d.ts +11 -1
  82. package/lib/runjs-context/snippets/index.js +61 -40
  83. package/lib/runjs-context/snippets/scene/block/add-event-listener.snippet.js +10 -7
  84. package/lib/runjs-context/snippets/scene/block/api-fetch-render-list.snippet.js +3 -3
  85. package/lib/runjs-context/snippets/scene/block/chartjs-bar.snippet.js +2 -2
  86. package/lib/runjs-context/snippets/scene/block/echarts-init.snippet.js +2 -2
  87. package/lib/runjs-context/snippets/scene/block/render-iframe.snippet.js +2 -2
  88. package/lib/runjs-context/snippets/scene/block/render-react.snippet.js +1 -1
  89. package/lib/runjs-context/snippets/scene/block/render-statistics.snippet.js +1 -1
  90. package/lib/runjs-context/snippets/scene/block/render-timeline.snippet.js +1 -1
  91. package/lib/runjs-context/snippets/scene/block/resource-example.snippet.js +5 -5
  92. package/lib/runjs-context/snippets/scene/block/three-users-orbit.snippet.js +6 -6
  93. package/lib/runjs-context/snippets/scene/block/vue-component.snippet.js +3 -4
  94. package/lib/runjs-context/snippets/scene/detail/color-by-value.snippet.js +1 -1
  95. package/lib/runjs-context/snippets/scene/detail/copy-to-clipboard.snippet.js +20 -3
  96. package/lib/runjs-context/snippets/scene/detail/format-number.snippet.js +1 -1
  97. package/lib/runjs-context/snippets/scene/detail/innerHTML-value.snippet.js +1 -1
  98. package/lib/runjs-context/snippets/scene/detail/percentage-bar.snippet.js +3 -3
  99. package/lib/runjs-context/snippets/scene/detail/relative-time.snippet.js +3 -3
  100. package/lib/runjs-context/snippets/scene/detail/status-tag.snippet.js +2 -2
  101. package/lib/runjs-context/snippets/scene/form/cascade-select.snippet.js +1 -1
  102. package/lib/runjs-context/snippets/scene/form/render-basic.snippet.js +2 -2
  103. package/lib/runjs-context/snippets/scene/table/cell-open-dialog.snippet.js +6 -3
  104. package/lib/runjs-context/snippets/scene/table/concat-fields.snippet.js +3 -1
  105. package/lib/runjsLibs.d.ts +28 -0
  106. package/lib/runjsLibs.js +532 -0
  107. package/lib/scheduler/ModelOperationScheduler.d.ts +7 -1
  108. package/lib/scheduler/ModelOperationScheduler.js +28 -23
  109. package/lib/server.d.ts +10 -0
  110. package/lib/server.js +32 -0
  111. package/lib/types.d.ts +296 -1
  112. package/lib/utils/associationObjectVariable.d.ts +2 -2
  113. package/lib/utils/createCollectionContextMeta.js +1 -0
  114. package/lib/utils/createEphemeralContext.js +2 -2
  115. package/lib/utils/dateVariable.d.ts +16 -0
  116. package/lib/utils/dateVariable.js +380 -0
  117. package/lib/utils/exceptions.d.ts +7 -0
  118. package/lib/utils/exceptions.js +10 -0
  119. package/lib/utils/index.d.ts +8 -3
  120. package/lib/utils/index.js +49 -0
  121. package/lib/utils/params-resolvers.js +16 -9
  122. package/lib/utils/parsePathnameToViewParams.js +1 -1
  123. package/lib/utils/resolveModuleUrl.d.ts +58 -0
  124. package/lib/utils/resolveModuleUrl.js +65 -0
  125. package/lib/utils/resolveRunJSObjectValues.d.ts +16 -0
  126. package/lib/utils/resolveRunJSObjectValues.js +61 -0
  127. package/lib/utils/runjsModuleLoader.d.ts +58 -0
  128. package/lib/utils/runjsModuleLoader.js +422 -0
  129. package/lib/utils/runjsTemplateCompat.d.ts +35 -0
  130. package/lib/utils/runjsTemplateCompat.js +743 -0
  131. package/lib/utils/runjsValue.d.ts +29 -0
  132. package/lib/utils/runjsValue.js +275 -0
  133. package/lib/utils/safeGlobals.d.ts +18 -8
  134. package/lib/utils/safeGlobals.js +164 -17
  135. package/lib/utils/schema-utils.d.ts +17 -1
  136. package/lib/utils/schema-utils.js +80 -0
  137. package/lib/views/FlowView.d.ts +7 -1
  138. package/lib/views/createViewMeta.d.ts +0 -7
  139. package/lib/views/createViewMeta.js +19 -70
  140. package/lib/views/index.d.ts +1 -2
  141. package/lib/views/index.js +4 -3
  142. package/lib/views/runViewBeforeClose.d.ts +10 -0
  143. package/lib/views/runViewBeforeClose.js +45 -0
  144. package/lib/views/useDialog.d.ts +2 -1
  145. package/lib/views/useDialog.js +28 -6
  146. package/lib/views/useDrawer.d.ts +2 -1
  147. package/lib/views/useDrawer.js +27 -5
  148. package/lib/views/usePage.d.ts +6 -1
  149. package/lib/views/usePage.js +53 -9
  150. package/lib/views/usePopover.js +4 -1
  151. package/lib/views/viewEvents.d.ts +17 -0
  152. package/lib/views/viewEvents.js +90 -0
  153. package/package.json +5 -5
  154. package/server.d.ts +1 -0
  155. package/server.js +1 -0
  156. package/src/BlockScopedFlowEngine.ts +2 -5
  157. package/src/FlowSchemaRegistry.ts +1799 -0
  158. package/src/JSRunner.ts +111 -5
  159. package/src/ViewScopedFlowEngine.ts +8 -0
  160. package/src/__tests__/FlowSchemaRegistry.test.ts +1951 -0
  161. package/src/__tests__/JSRunner.test.ts +91 -1
  162. package/src/__tests__/createViewMeta.popup.test.ts +62 -1
  163. package/src/__tests__/flow-engine.test.ts +48 -0
  164. package/src/__tests__/flowContext.test.ts +693 -1
  165. package/src/__tests__/flowEngine.dataSourceDirty.test.ts +63 -0
  166. package/src/__tests__/flowEngine.modelLoaders.test.ts +249 -0
  167. package/src/__tests__/flowEngine.saveModel.test.ts +4 -0
  168. package/src/__tests__/flowModel.openView.navigation.test.ts +28 -0
  169. package/src/__tests__/flowRunJSContextDefine.test.ts +63 -0
  170. package/src/__tests__/flowRuntimeContext.test.ts +2 -1
  171. package/src/__tests__/flowSettings.open.test.tsx +123 -19
  172. package/src/__tests__/flowSettings.test.ts +94 -15
  173. package/src/__tests__/provider.test.tsx +0 -5
  174. package/src/__tests__/renderHiddenInConfig.test.tsx +6 -6
  175. package/src/__tests__/runjsContext.test.ts +26 -7
  176. package/src/__tests__/runjsContextImplementations.test.ts +34 -3
  177. package/src/__tests__/runjsContextRuntime.test.ts +5 -3
  178. package/src/__tests__/runjsContributions.test.ts +89 -0
  179. package/src/__tests__/runjsExternalLibs.test.ts +242 -0
  180. package/src/__tests__/runjsLibsLazyLoading.test.ts +44 -0
  181. package/src/__tests__/runjsLocales.test.ts +4 -1
  182. package/src/__tests__/runjsPreprocessDefault.test.ts +72 -0
  183. package/src/__tests__/runjsRuntimeFeatures.test.ts +166 -0
  184. package/src/__tests__/runjsSnippets.test.ts +40 -3
  185. package/src/__tests__/viewScopedFlowEngine.test.ts +3 -3
  186. package/src/acl/Acl.tsx +3 -3
  187. package/src/components/FlowContextSelector.tsx +208 -12
  188. package/src/components/MobilePopup.tsx +4 -2
  189. package/src/components/__tests__/flow-model-render-error-fallback.test.tsx +3 -3
  190. package/src/components/__tests__/gridDragPlanner.test.ts +229 -1
  191. package/src/components/dnd/gridDragPlanner.ts +68 -2
  192. package/src/components/settings/wrappers/component/SwitchWithTitle.tsx +2 -1
  193. package/src/components/settings/wrappers/component/__tests__/InlineControls.test.tsx +74 -0
  194. package/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +109 -16
  195. package/src/components/settings/wrappers/contextual/FlowsContextMenu.tsx +41 -7
  196. package/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx +31 -4
  197. package/src/components/settings/wrappers/contextual/__tests__/DefaultSettingsIcon.test.tsx +157 -5
  198. package/src/components/subModel/AddSubModelButton.tsx +32 -2
  199. package/src/components/subModel/__tests__/AddSubModelButton.test.tsx +143 -32
  200. package/src/components/subModel/utils.ts +1 -1
  201. package/src/components/variables/VariableInput.tsx +12 -4
  202. package/src/components/variables/VariableTag.tsx +54 -45
  203. package/src/components/variables/__tests__/FlowContextSelector.test.tsx +260 -3
  204. package/src/components/variables/__tests__/VariableTag.test.tsx +50 -0
  205. package/src/components/variables/__tests__/utils.test.ts +81 -3
  206. package/src/components/variables/utils.ts +67 -6
  207. package/src/data-source/index.ts +88 -110
  208. package/src/executor/FlowExecutor.ts +230 -28
  209. package/src/executor/__tests__/flowExecutor.test.ts +123 -0
  210. package/src/flow-schema-registry/fieldBinding.ts +171 -0
  211. package/src/flow-schema-registry/modelPatches.ts +260 -0
  212. package/src/flow-schema-registry/schemaInference.ts +210 -0
  213. package/src/flow-schema-registry/utils.ts +268 -0
  214. package/src/flowContext.ts +2989 -212
  215. package/src/flowEngine.ts +434 -23
  216. package/src/flowI18n.ts +7 -5
  217. package/src/flowSettings.ts +58 -18
  218. package/src/index.ts +15 -1
  219. package/src/lazy-helper.tsx +57 -0
  220. package/src/locale/en-US.json +9 -2
  221. package/src/locale/zh-CN.json +8 -1
  222. package/src/models/CollectionFieldModel.tsx +3 -1
  223. package/src/models/DisplayItemModel.tsx +1 -1
  224. package/src/models/EditableItemModel.tsx +1 -1
  225. package/src/models/FilterableItemModel.tsx +1 -1
  226. package/src/models/__tests__/dispatchEvent.when.test.ts +768 -0
  227. package/src/models/__tests__/flowModel.clone.test.ts +416 -0
  228. package/src/models/__tests__/flowModel.test.ts +20 -4
  229. package/src/models/flowModel.tsx +112 -7
  230. package/src/provider.tsx +9 -7
  231. package/src/resources/__tests__/multiRecordResource.test.ts +44 -0
  232. package/src/resources/__tests__/sqlResource.test.ts +60 -0
  233. package/src/resources/baseRecordResource.ts +31 -0
  234. package/src/resources/multiRecordResource.ts +11 -4
  235. package/src/resources/singleRecordResource.ts +3 -0
  236. package/src/resources/sqlResource.ts +11 -6
  237. package/src/runjs-context/contexts/FormJSFieldItemRunJSContext.ts +10 -0
  238. package/src/runjs-context/contexts/JSBlockRunJSContext.ts +6 -2
  239. package/src/runjs-context/contexts/JSEditableFieldRunJSContext.ts +106 -0
  240. package/src/runjs-context/contexts/JSItemRunJSContext.ts +10 -0
  241. package/src/runjs-context/contexts/base.ts +715 -44
  242. package/src/runjs-context/contributions.ts +88 -0
  243. package/src/runjs-context/helpers.ts +11 -1
  244. package/src/runjs-context/registry.ts +1 -1
  245. package/src/runjs-context/setup.ts +25 -9
  246. package/src/runjs-context/snippets/global/api-request.snippet.ts +3 -3
  247. package/src/runjs-context/snippets/global/import-esm.snippet.ts +2 -3
  248. package/src/runjs-context/snippets/global/query-selector.snippet.ts +8 -3
  249. package/src/runjs-context/snippets/global/require-amd.snippet.ts +1 -1
  250. package/src/runjs-context/snippets/index.ts +75 -41
  251. package/src/runjs-context/snippets/scene/block/add-event-listener.snippet.ts +11 -13
  252. package/src/runjs-context/snippets/scene/block/api-fetch-render-list.snippet.ts +3 -3
  253. package/src/runjs-context/snippets/scene/block/chartjs-bar.snippet.ts +2 -2
  254. package/src/runjs-context/snippets/scene/block/echarts-init.snippet.ts +2 -2
  255. package/src/runjs-context/snippets/scene/block/render-iframe.snippet.ts +2 -2
  256. package/src/runjs-context/snippets/scene/block/render-react.snippet.ts +1 -1
  257. package/src/runjs-context/snippets/scene/block/render-statistics.snippet.ts +1 -1
  258. package/src/runjs-context/snippets/scene/block/render-timeline.snippet.ts +1 -1
  259. package/src/runjs-context/snippets/scene/block/resource-example.snippet.ts +6 -11
  260. package/src/runjs-context/snippets/scene/block/three-users-orbit.snippet.ts +6 -6
  261. package/src/runjs-context/snippets/scene/block/vue-component.snippet.ts +3 -4
  262. package/src/runjs-context/snippets/scene/detail/color-by-value.snippet.ts +1 -1
  263. package/src/runjs-context/snippets/scene/detail/copy-to-clipboard.snippet.ts +20 -3
  264. package/src/runjs-context/snippets/scene/detail/format-number.snippet.ts +1 -1
  265. package/src/runjs-context/snippets/scene/detail/innerHTML-value.snippet.ts +1 -1
  266. package/src/runjs-context/snippets/scene/detail/percentage-bar.snippet.ts +3 -3
  267. package/src/runjs-context/snippets/scene/detail/relative-time.snippet.ts +3 -3
  268. package/src/runjs-context/snippets/scene/detail/status-tag.snippet.ts +2 -2
  269. package/src/runjs-context/snippets/scene/form/cascade-select.snippet.ts +1 -1
  270. package/src/runjs-context/snippets/scene/form/render-basic.snippet.ts +3 -8
  271. package/src/runjs-context/snippets/scene/table/cell-open-dialog.snippet.ts +6 -3
  272. package/src/runjs-context/snippets/scene/table/concat-fields.snippet.ts +3 -1
  273. package/src/runjsLibs.ts +622 -0
  274. package/src/scheduler/ModelOperationScheduler.ts +41 -24
  275. package/src/server.ts +11 -0
  276. package/src/types.ts +359 -1
  277. package/src/utils/__tests__/dateVariable.test.ts +101 -0
  278. package/src/utils/__tests__/params-resolvers.test.ts +40 -0
  279. package/src/utils/__tests__/parsePathnameToViewParams.test.ts +7 -0
  280. package/src/utils/__tests__/runjsRequireAsyncAutoWhitelist.test.ts +38 -0
  281. package/src/utils/__tests__/runjsTemplateCompat.test.ts +159 -0
  282. package/src/utils/__tests__/runjsValue.test.ts +44 -0
  283. package/src/utils/__tests__/safeGlobals.test.ts +57 -2
  284. package/src/utils/__tests__/utils.test.ts +157 -0
  285. package/src/utils/associationObjectVariable.ts +2 -2
  286. package/src/utils/createCollectionContextMeta.ts +1 -0
  287. package/src/utils/createEphemeralContext.ts +5 -4
  288. package/src/utils/dateVariable.ts +397 -0
  289. package/src/utils/exceptions.ts +11 -0
  290. package/src/utils/index.ts +38 -3
  291. package/src/utils/params-resolvers.ts +23 -9
  292. package/src/utils/parsePathnameToViewParams.ts +2 -2
  293. package/src/utils/resolveModuleUrl.ts +91 -0
  294. package/src/utils/resolveRunJSObjectValues.ts +46 -0
  295. package/src/utils/runjsModuleLoader.ts +553 -0
  296. package/src/utils/runjsTemplateCompat.ts +828 -0
  297. package/src/utils/runjsValue.ts +287 -0
  298. package/src/utils/safeGlobals.ts +188 -17
  299. package/src/utils/schema-utils.ts +109 -1
  300. package/src/views/FlowView.tsx +11 -1
  301. package/src/views/__tests__/FlowView.usePage.test.tsx +54 -1
  302. package/src/views/__tests__/runViewBeforeClose.test.ts +30 -0
  303. package/src/views/__tests__/useDialog.closeDestroy.test.tsx +44 -16
  304. package/src/views/__tests__/viewEvents.resolveOpenerEngine.test.ts +28 -0
  305. package/src/views/createViewMeta.ts +22 -75
  306. package/src/views/index.tsx +1 -2
  307. package/src/views/runViewBeforeClose.ts +19 -0
  308. package/src/views/useDialog.tsx +34 -5
  309. package/src/views/useDrawer.tsx +33 -4
  310. package/src/views/usePage.tsx +63 -8
  311. package/src/views/usePopover.tsx +4 -1
  312. package/src/views/viewEvents.ts +55 -0
@@ -0,0 +1,1799 @@
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 _ from 'lodash';
11
+ import type {
12
+ ActionDefinition,
13
+ FlowActionSchemaContribution,
14
+ FlowFieldBindingContextContribution,
15
+ FlowFieldBindingContribution,
16
+ FlowFieldModelCompatibility,
17
+ FlowSchemaBundleDocument,
18
+ FlowSchemaBundleNode,
19
+ FlowSchemaBundleSlotCatalog,
20
+ FlowSchemaContextEdge,
21
+ FlowSchemaDocs,
22
+ FlowDynamicHint,
23
+ FlowSchemaInventoryContribution,
24
+ FlowJsonSchema,
25
+ FlowModelSchemaPatch,
26
+ FlowModelSchemaContribution,
27
+ FlowModelMeta,
28
+ FlowSchemaCoverage,
29
+ FlowSchemaDetail,
30
+ FlowSchemaDocument,
31
+ FlowModelSchemaExposure,
32
+ FlowSchemaPublicDocument,
33
+ FlowSubModelSlotSchema,
34
+ ModelConstructor,
35
+ StepDefinition,
36
+ } from './types';
37
+ import {
38
+ buildFieldModelCompatibility,
39
+ matchesFieldBinding,
40
+ normalizeFieldBindingContextContribution,
41
+ normalizeFieldBindingContribution,
42
+ type RegisteredFieldBinding,
43
+ type RegisteredFieldBindingContext,
44
+ } from './flow-schema-registry/fieldBinding';
45
+ import {
46
+ applyModelSchemaPatch,
47
+ matchesDescendantSchemaPatch,
48
+ normalizeSubModelSlots,
49
+ resolveChildSchemaPatch,
50
+ } from './flow-schema-registry/modelPatches';
51
+ import { inferParamsSchemaFromUiSchema, type StepSchemaResolution } from './flow-schema-registry/schemaInference';
52
+ import {
53
+ JSON_SCHEMA_DRAFT_07,
54
+ buildSkeletonFromSchema,
55
+ collectAllowedUses,
56
+ createFlowHint,
57
+ deepFreezePlainGraph,
58
+ hashString,
59
+ mergeSchemas,
60
+ normalizeSchemaDocs,
61
+ normalizeSchemaHints,
62
+ normalizeStringArray,
63
+ stableStringify,
64
+ toSchemaTitle,
65
+ } from './flow-schema-registry/utils';
66
+
67
+ export type RegisteredActionSchema = {
68
+ name: string;
69
+ title?: string;
70
+ definition?: ActionDefinition;
71
+ schema?: FlowJsonSchema;
72
+ docs: FlowSchemaDocs;
73
+ coverage: FlowSchemaCoverage;
74
+ dynamicHints: FlowDynamicHint[];
75
+ };
76
+
77
+ export type RegisteredModelSchema = {
78
+ use: string;
79
+ modelClass?: ModelConstructor;
80
+ stepParamsSchema?: FlowJsonSchema;
81
+ flowRegistrySchema?: FlowJsonSchema;
82
+ subModelSlots?: Record<string, FlowSubModelSlotSchema>;
83
+ flowRegistrySchemaPatch?: FlowJsonSchema;
84
+ title?: string;
85
+ examples: any[];
86
+ docs: FlowSchemaDocs;
87
+ skeleton?: any;
88
+ dynamicHints: FlowDynamicHint[];
89
+ coverage: FlowSchemaCoverage;
90
+ exposure: FlowModelSchemaExposure;
91
+ abstract: boolean;
92
+ allowDirectUse: boolean;
93
+ suggestedUses: string[];
94
+ };
95
+
96
+ type ModelPatchContribution = {
97
+ patch: FlowModelSchemaPatch;
98
+ source: FlowSchemaCoverage['source'];
99
+ strict?: boolean;
100
+ };
101
+
102
+ export class FlowSchemaRegistry {
103
+ private readonly modelSchemas = new Map<string, RegisteredModelSchema>();
104
+ private readonly actionSchemas = new Map<string, RegisteredActionSchema>();
105
+ private readonly fieldBindingContexts = new Map<string, RegisteredFieldBindingContext>();
106
+ private readonly fieldBindings = new Map<string, RegisteredFieldBinding[]>();
107
+ private readonly resolvedModelCache = new Map<string, RegisteredModelSchema>();
108
+ private readonly modelSnapshotSchemaCache = new Map<string, FlowJsonSchema>();
109
+ private readonly compactModelSnapshotSchemaCache = new Map<string, FlowJsonSchema>();
110
+ private readonly modelSchemaHashCache = new Map<string, string>();
111
+ private readonly compactModelSchemaHashCache = new Map<string, string>();
112
+ private readonly modelDocumentCache = new Map<string, FlowSchemaDocument>();
113
+ private readonly modelLocalDynamicHintsCache = new Map<string, FlowDynamicHint[]>();
114
+ private readonly publicModelDocumentCache = new Map<string, FlowSchemaPublicDocument>();
115
+ private readonly publicTreeRoots = new Set<string>();
116
+ private readonly slotUseExpansions = new Map<string, string[]>();
117
+
118
+ private invalidateDerivedCaches() {
119
+ this.resolvedModelCache.clear();
120
+ this.modelSnapshotSchemaCache.clear();
121
+ this.compactModelSnapshotSchemaCache.clear();
122
+ this.modelSchemaHashCache.clear();
123
+ this.compactModelSchemaHashCache.clear();
124
+ this.modelDocumentCache.clear();
125
+ this.modelLocalDynamicHintsCache.clear();
126
+ this.publicModelDocumentCache.clear();
127
+ }
128
+
129
+ registerAction(action: ActionDefinition | ({ name: string } & Partial<ActionDefinition>)) {
130
+ const name = String(action?.name || '').trim();
131
+ if (!name) return;
132
+
133
+ const previous = this.actionSchemas.get(name);
134
+ const inferred = inferParamsSchemaFromUiSchema(name, action.uiSchema as any, `actions.${name}`);
135
+ const mergedSchema = mergeSchemas(action.paramsSchema || inferred.schema, action.paramsSchemaPatch);
136
+ const explicit = !!action.paramsSchema || !!action.paramsSchemaPatch;
137
+ const coverageStatus: FlowSchemaCoverage['status'] = explicit
138
+ ? inferred.schema
139
+ ? 'mixed'
140
+ : 'manual'
141
+ : inferred.coverage;
142
+ const docs = normalizeSchemaDocs({
143
+ ...previous?.docs,
144
+ ...action.schemaDocs,
145
+ examples: action.schemaDocs?.examples || previous?.docs?.examples,
146
+ dynamicHints: [...(previous?.docs?.dynamicHints || []), ...(action.schemaDocs?.dynamicHints || [])],
147
+ });
148
+
149
+ this.actionSchemas.set(name, {
150
+ name,
151
+ title: action.title,
152
+ definition: action as ActionDefinition,
153
+ schema: mergedSchema,
154
+ coverage: {
155
+ status: mergedSchema ? coverageStatus : 'unresolved',
156
+ source: previous?.coverage.source || 'official',
157
+ strict: previous?.coverage.strict,
158
+ },
159
+ docs,
160
+ dynamicHints: normalizeSchemaHints([
161
+ ...(previous?.dynamicHints || []),
162
+ ...(inferred.hints || []),
163
+ ...(docs.dynamicHints || []),
164
+ ]),
165
+ });
166
+ this.invalidateDerivedCaches();
167
+ }
168
+
169
+ registerActions(actions: Record<string, ActionDefinition>) {
170
+ for (const action of Object.values(actions || {})) {
171
+ this.registerAction(action);
172
+ }
173
+ }
174
+
175
+ registerActionContribution(contribution: FlowActionSchemaContribution) {
176
+ const name = String(contribution?.name || '').trim();
177
+ if (!name) return;
178
+
179
+ const previous = this.actionSchemas.get(name);
180
+ const docs = normalizeSchemaDocs({
181
+ ...previous?.docs,
182
+ ...contribution.docs,
183
+ examples: contribution.docs?.examples || previous?.docs?.examples,
184
+ dynamicHints: [...(previous?.docs?.dynamicHints || []), ...(contribution.docs?.dynamicHints || [])],
185
+ commonPatterns: contribution.docs?.commonPatterns || previous?.docs?.commonPatterns,
186
+ antiPatterns: contribution.docs?.antiPatterns || previous?.docs?.antiPatterns,
187
+ minimalExample:
188
+ contribution.docs?.minimalExample !== undefined
189
+ ? contribution.docs.minimalExample
190
+ : previous?.docs?.minimalExample,
191
+ });
192
+ this.actionSchemas.set(name, {
193
+ name,
194
+ title: contribution.title || previous?.title,
195
+ definition: previous?.definition,
196
+ schema: contribution.paramsSchema ? _.cloneDeep(contribution.paramsSchema) : previous?.schema,
197
+ docs,
198
+ coverage: {
199
+ status: contribution.paramsSchema ? 'manual' : previous?.coverage.status || 'unresolved',
200
+ source: contribution.source || previous?.coverage.source || 'official',
201
+ strict: contribution.strict ?? previous?.coverage.strict,
202
+ },
203
+ dynamicHints: normalizeSchemaHints([...(previous?.dynamicHints || []), ...(docs.dynamicHints || [])]),
204
+ });
205
+ this.invalidateDerivedCaches();
206
+ }
207
+
208
+ registerActionContributions(
209
+ contributions: FlowActionSchemaContribution[] | Record<string, FlowActionSchemaContribution>,
210
+ ) {
211
+ const values = Array.isArray(contributions) ? contributions : Object.values(contributions || {});
212
+ for (const contribution of values) {
213
+ this.registerActionContribution(contribution);
214
+ }
215
+ }
216
+
217
+ registerFieldBindingContext(contribution: FlowFieldBindingContextContribution, fallbackName?: string) {
218
+ const normalized = normalizeFieldBindingContextContribution(contribution, fallbackName);
219
+ if (!normalized) {
220
+ return;
221
+ }
222
+
223
+ const previous = this.fieldBindingContexts.get(normalized.name);
224
+ this.fieldBindingContexts.set(normalized.name, {
225
+ name: normalized.name,
226
+ inherits: _.uniq([...(previous?.inherits || []), ...normalized.inherits]),
227
+ });
228
+ this.invalidateDerivedCaches();
229
+ }
230
+
231
+ registerFieldBindingContexts(
232
+ contributions:
233
+ | FlowFieldBindingContextContribution[]
234
+ | Record<string, FlowFieldBindingContextContribution>
235
+ | undefined,
236
+ ) {
237
+ if (!contributions) {
238
+ return;
239
+ }
240
+
241
+ if (Array.isArray(contributions)) {
242
+ for (const contribution of contributions) {
243
+ this.registerFieldBindingContext(contribution);
244
+ }
245
+ return;
246
+ }
247
+
248
+ for (const [name, contribution] of Object.entries(contributions)) {
249
+ if (!contribution) {
250
+ continue;
251
+ }
252
+ this.registerFieldBindingContext(contribution, name);
253
+ }
254
+ }
255
+
256
+ registerFieldBinding(contribution: FlowFieldBindingContribution, source: FlowSchemaCoverage['source'] = 'official') {
257
+ const normalized = normalizeFieldBindingContribution(contribution, source);
258
+ if (!normalized) {
259
+ return;
260
+ }
261
+
262
+ const bindings = this.fieldBindings.get(normalized.context) || [];
263
+ bindings.push(normalized);
264
+ this.fieldBindings.set(normalized.context, bindings);
265
+ this.invalidateDerivedCaches();
266
+ }
267
+
268
+ registerFieldBindings(
269
+ contributions:
270
+ | FlowFieldBindingContribution[]
271
+ | Record<string, FlowFieldBindingContribution | FlowFieldBindingContribution[]>
272
+ | undefined,
273
+ source: FlowSchemaCoverage['source'] = 'official',
274
+ ) {
275
+ if (!contributions) {
276
+ return;
277
+ }
278
+
279
+ if (Array.isArray(contributions)) {
280
+ for (const contribution of contributions) {
281
+ this.registerFieldBinding(contribution, source);
282
+ }
283
+ return;
284
+ }
285
+
286
+ for (const [context, contribution] of Object.entries(contributions)) {
287
+ if (!contribution) {
288
+ continue;
289
+ }
290
+
291
+ const items = Array.isArray(contribution) ? contribution : [contribution];
292
+ for (const item of items) {
293
+ this.registerFieldBinding(
294
+ {
295
+ ...item,
296
+ context: item.context || context,
297
+ },
298
+ source,
299
+ );
300
+ }
301
+ }
302
+ }
303
+
304
+ registerModel(use: string, options: Partial<RegisteredModelSchema>) {
305
+ const name = String(use || '').trim();
306
+ if (!name) return;
307
+ const previous = this.modelSchemas.get(name);
308
+ this.modelSchemas.set(name, {
309
+ use: name,
310
+ title: options.title || previous?.title,
311
+ modelClass: options.modelClass || previous?.modelClass,
312
+ stepParamsSchema: options.stepParamsSchema || previous?.stepParamsSchema,
313
+ flowRegistrySchema: options.flowRegistrySchema || previous?.flowRegistrySchema,
314
+ subModelSlots: normalizeSubModelSlots(options.subModelSlots || previous?.subModelSlots),
315
+ flowRegistrySchemaPatch: options.flowRegistrySchemaPatch || previous?.flowRegistrySchemaPatch,
316
+ examples: options.examples || previous?.examples || [],
317
+ docs: normalizeSchemaDocs({
318
+ ...previous?.docs,
319
+ ...options.docs,
320
+ examples: options.docs?.examples || previous?.docs?.examples,
321
+ dynamicHints: [...(previous?.docs?.dynamicHints || []), ...(options.docs?.dynamicHints || [])],
322
+ commonPatterns: options.docs?.commonPatterns || previous?.docs?.commonPatterns,
323
+ antiPatterns: options.docs?.antiPatterns || previous?.docs?.antiPatterns,
324
+ minimalExample:
325
+ options.docs?.minimalExample !== undefined ? options.docs.minimalExample : previous?.docs?.minimalExample,
326
+ }),
327
+ skeleton: options.skeleton !== undefined ? _.cloneDeep(options.skeleton) : previous?.skeleton,
328
+ dynamicHints: normalizeSchemaHints([...(previous?.dynamicHints || []), ...(options.dynamicHints || [])]),
329
+ coverage: options.coverage || previous?.coverage || { status: 'unresolved', source: 'third-party' },
330
+ exposure: options.exposure ?? previous?.exposure ?? 'public',
331
+ abstract: options.abstract ?? previous?.abstract ?? false,
332
+ allowDirectUse: options.allowDirectUse ?? previous?.allowDirectUse ?? true,
333
+ suggestedUses: normalizeStringArray(options.suggestedUses || previous?.suggestedUses),
334
+ });
335
+ this.invalidateDerivedCaches();
336
+ }
337
+
338
+ registerModelContribution(contribution: FlowModelSchemaContribution) {
339
+ const use = String(contribution?.use || '').trim();
340
+ if (!use) return;
341
+
342
+ const previous = this.modelSchemas.get(use);
343
+ const hasSchemaContribution =
344
+ !!contribution.stepParamsSchema ||
345
+ !!contribution.flowRegistrySchema ||
346
+ !!contribution.flowRegistrySchemaPatch ||
347
+ !!contribution.subModelSlots;
348
+ const docs = normalizeSchemaDocs({
349
+ ...previous?.docs,
350
+ ...contribution.docs,
351
+ examples: contribution.examples || contribution.docs?.examples || previous?.docs?.examples,
352
+ dynamicHints: [...(previous?.docs?.dynamicHints || []), ...(contribution.docs?.dynamicHints || [])],
353
+ commonPatterns: contribution.docs?.commonPatterns || previous?.docs?.commonPatterns,
354
+ antiPatterns: contribution.docs?.antiPatterns || previous?.docs?.antiPatterns,
355
+ minimalExample:
356
+ contribution.docs?.minimalExample !== undefined
357
+ ? contribution.docs.minimalExample
358
+ : previous?.docs?.minimalExample,
359
+ });
360
+ this.registerModel(use, {
361
+ title: contribution.title,
362
+ stepParamsSchema: contribution.stepParamsSchema ? _.cloneDeep(contribution.stepParamsSchema) : undefined,
363
+ flowRegistrySchema: contribution.flowRegistrySchema ? _.cloneDeep(contribution.flowRegistrySchema) : undefined,
364
+ subModelSlots: contribution.subModelSlots ? normalizeSubModelSlots(contribution.subModelSlots) : undefined,
365
+ flowRegistrySchemaPatch: contribution.flowRegistrySchemaPatch
366
+ ? _.cloneDeep(contribution.flowRegistrySchemaPatch)
367
+ : undefined,
368
+ examples: contribution.examples || docs.examples || [],
369
+ docs,
370
+ skeleton: contribution.skeleton,
371
+ dynamicHints: [...(contribution.dynamicHints || []), ...(docs.dynamicHints || [])],
372
+ coverage: hasSchemaContribution
373
+ ? {
374
+ status: previous?.coverage.status === 'auto' ? 'mixed' : 'manual',
375
+ source: contribution.source || previous?.coverage.source || 'official',
376
+ strict: contribution.strict ?? previous?.coverage.strict,
377
+ }
378
+ : previous?.coverage || {
379
+ status: 'unresolved',
380
+ source: contribution.source || 'official',
381
+ strict: contribution.strict,
382
+ },
383
+ exposure: contribution.exposure,
384
+ abstract: contribution.abstract,
385
+ allowDirectUse: contribution.allowDirectUse,
386
+ suggestedUses: contribution.suggestedUses,
387
+ });
388
+ }
389
+
390
+ registerModelContributions(
391
+ contributions: FlowModelSchemaContribution[] | Record<string, FlowModelSchemaContribution>,
392
+ ) {
393
+ const values = Array.isArray(contributions) ? contributions : Object.values(contributions || {});
394
+ for (const contribution of values) {
395
+ this.registerModelContribution(contribution);
396
+ }
397
+ }
398
+
399
+ registerModelClass(use: string, modelClass: ModelConstructor) {
400
+ const meta = ((modelClass as any).meta || {}) as FlowModelMeta;
401
+ const schemaMeta = meta.schema || {};
402
+ const inferredSlots = this.inferSubModelSlotsFromModelClass(use, modelClass);
403
+ const inferredHints = this.collectModelDynamicHints(use, modelClass, meta);
404
+ const hasManual =
405
+ !!schemaMeta.stepParamsSchema ||
406
+ !!schemaMeta.flowRegistrySchema ||
407
+ !!schemaMeta.subModelSlots ||
408
+ !!schemaMeta.flowRegistrySchemaPatch;
409
+ const hasAuto = !!Object.keys(inferredSlots).length;
410
+
411
+ this.registerModel(use, {
412
+ modelClass,
413
+ title: toSchemaTitle(meta.label, use),
414
+ stepParamsSchema: schemaMeta.stepParamsSchema,
415
+ flowRegistrySchema: schemaMeta.flowRegistrySchema,
416
+ subModelSlots: Object.keys(schemaMeta.subModelSlots || {}).length ? schemaMeta.subModelSlots : inferredSlots,
417
+ flowRegistrySchemaPatch: schemaMeta.flowRegistrySchemaPatch,
418
+ examples: schemaMeta.examples || schemaMeta.docs?.examples || [],
419
+ docs: schemaMeta.docs,
420
+ skeleton: schemaMeta.skeleton,
421
+ dynamicHints: [...(schemaMeta.dynamicHints || []), ...(schemaMeta.docs?.dynamicHints || []), ...inferredHints],
422
+ coverage: {
423
+ status: hasManual && hasAuto ? 'mixed' : hasManual ? 'manual' : hasAuto ? 'auto' : 'unresolved',
424
+ source: schemaMeta.source || 'official',
425
+ strict: schemaMeta.strict,
426
+ },
427
+ exposure: schemaMeta.exposure,
428
+ abstract: schemaMeta.abstract,
429
+ allowDirectUse: schemaMeta.allowDirectUse,
430
+ suggestedUses: schemaMeta.suggestedUses,
431
+ });
432
+ }
433
+
434
+ registerModels(models: Record<string, ModelConstructor | undefined>) {
435
+ for (const [use, modelClass] of Object.entries(models || {})) {
436
+ if (modelClass) {
437
+ this.registerModelClass(use, modelClass);
438
+ }
439
+ }
440
+ }
441
+
442
+ registerInventory(inventory: FlowSchemaInventoryContribution | undefined, _source: FlowSchemaCoverage['source']) {
443
+ if (!inventory) {
444
+ return;
445
+ }
446
+
447
+ for (const use of normalizeStringArray(inventory.publicTreeRoots)) {
448
+ this.publicTreeRoots.add(use);
449
+ }
450
+
451
+ for (const item of inventory.slotUseExpansions || []) {
452
+ const parentUse = String(item?.parentUse || '').trim();
453
+ const slotKey = String(item?.slotKey || '').trim();
454
+ const uses = normalizeStringArray(item?.uses);
455
+ if (!parentUse || !slotKey || uses.length === 0) {
456
+ continue;
457
+ }
458
+
459
+ const key = this.createSlotUseExpansionKey(parentUse, slotKey);
460
+ this.slotUseExpansions.set(key, _.uniq([...(this.slotUseExpansions.get(key) || []), ...uses]));
461
+ }
462
+ this.invalidateDerivedCaches();
463
+ }
464
+
465
+ resolveFieldBindingCandidates(
466
+ context: string,
467
+ options: {
468
+ interface?: string;
469
+ fieldType?: string;
470
+ association?: boolean;
471
+ targetCollectionTemplate?: string;
472
+ } = {},
473
+ ) {
474
+ const contextChain = this.resolveFieldBindingContextChain(context);
475
+ const entries = contextChain.flatMap((contextName, contextIndex) =>
476
+ (this.fieldBindings.get(contextName) || []).map((binding, bindingIndex) => ({
477
+ binding,
478
+ contextIndex,
479
+ bindingIndex,
480
+ })),
481
+ );
482
+
483
+ const candidates = entries
484
+ .filter(({ binding }) => matchesFieldBinding(binding, options))
485
+ .filter(({ binding }) => this.hasQueryableModel(binding.use))
486
+ .sort((a, b) => {
487
+ if (a.binding.isDefault !== b.binding.isDefault) {
488
+ return a.binding.isDefault ? -1 : 1;
489
+ }
490
+
491
+ const aOrder = typeof a.binding.order === 'number' ? a.binding.order : Number.MAX_SAFE_INTEGER;
492
+ const bOrder = typeof b.binding.order === 'number' ? b.binding.order : Number.MAX_SAFE_INTEGER;
493
+ if (aOrder !== bOrder) {
494
+ return aOrder - bOrder;
495
+ }
496
+
497
+ if (a.contextIndex !== b.contextIndex) {
498
+ return a.contextIndex - b.contextIndex;
499
+ }
500
+
501
+ return a.bindingIndex - b.bindingIndex;
502
+ })
503
+ .map(({ binding }) => ({
504
+ use: binding.use,
505
+ defaultProps: binding.defaultProps === undefined ? undefined : _.cloneDeep(binding.defaultProps),
506
+ compatibility: buildFieldModelCompatibility(binding),
507
+ }));
508
+
509
+ return _.uniqBy(candidates, (candidate) => `${candidate.use}:${stableStringify(candidate.compatibility)}`);
510
+ }
511
+
512
+ getAction(name: string): RegisteredActionSchema | undefined {
513
+ return this.actionSchemas.get(name);
514
+ }
515
+
516
+ listActionNames(): string[] {
517
+ return Array.from(this.actionSchemas.keys()).sort();
518
+ }
519
+
520
+ getModel(use: string): RegisteredModelSchema | undefined {
521
+ return this.modelSchemas.get(use);
522
+ }
523
+
524
+ private resolveFieldBindingContextChain(context: string, visited = new Set<string>()): string[] {
525
+ const name = String(context || '').trim();
526
+ if (!name || visited.has(name)) {
527
+ return [];
528
+ }
529
+
530
+ visited.add(name);
531
+
532
+ const chain = [name];
533
+ const inherits = this.fieldBindingContexts.get(name)?.inherits || [];
534
+ for (const inheritedContext of inherits) {
535
+ for (const item of this.resolveFieldBindingContextChain(inheritedContext, visited)) {
536
+ if (!chain.includes(item)) {
537
+ chain.push(item);
538
+ }
539
+ }
540
+ }
541
+
542
+ return chain;
543
+ }
544
+
545
+ private resolveModelSchemaRef(use: string, contextChain: FlowSchemaContextEdge[] = []): RegisteredModelSchema {
546
+ const name = String(use || '').trim();
547
+ const cacheKey = `${name}::${stableStringify(contextChain)}`;
548
+ const cached = this.resolvedModelCache.get(cacheKey);
549
+ if (cached) {
550
+ return cached;
551
+ }
552
+
553
+ const registered = this.modelSchemas.get(name);
554
+ const resolved: RegisteredModelSchema = {
555
+ use: name,
556
+ title: registered?.title || name,
557
+ modelClass: registered?.modelClass,
558
+ stepParamsSchema: registered?.stepParamsSchema
559
+ ? _.cloneDeep(registered.stepParamsSchema)
560
+ : this.buildInferredStepParamsSchema(name),
561
+ flowRegistrySchema: registered?.flowRegistrySchema
562
+ ? _.cloneDeep(registered.flowRegistrySchema)
563
+ : this.buildInferredFlowRegistrySchema(name),
564
+ subModelSlots: normalizeSubModelSlots(registered?.subModelSlots),
565
+ flowRegistrySchemaPatch: registered?.flowRegistrySchemaPatch
566
+ ? _.cloneDeep(registered.flowRegistrySchemaPatch)
567
+ : undefined,
568
+ examples: _.cloneDeep(registered?.examples || []),
569
+ docs: normalizeSchemaDocs(registered?.docs),
570
+ skeleton: registered?.skeleton === undefined ? undefined : _.cloneDeep(registered.skeleton),
571
+ dynamicHints: normalizeSchemaHints(registered?.dynamicHints),
572
+ coverage: registered?.coverage || { status: 'unresolved', source: 'third-party' },
573
+ exposure: registered?.exposure || 'public',
574
+ abstract: registered?.abstract ?? false,
575
+ allowDirectUse: registered?.allowDirectUse ?? true,
576
+ suggestedUses: normalizeStringArray(registered?.suggestedUses),
577
+ };
578
+
579
+ for (const contribution of this.collectContextPatches(name, contextChain)) {
580
+ applyModelSchemaPatch(resolved, contribution.patch, contribution.source, contribution.strict);
581
+ }
582
+
583
+ const frozen = deepFreezePlainGraph(resolved);
584
+ this.resolvedModelCache.set(cacheKey, frozen);
585
+ return frozen;
586
+ }
587
+
588
+ resolveModelSchema(use: string, contextChain: FlowSchemaContextEdge[] = []): RegisteredModelSchema {
589
+ return _.cloneDeep(this.resolveModelSchemaRef(use, contextChain));
590
+ }
591
+
592
+ private isPublicModel(model?: Pick<RegisteredModelSchema, 'exposure'>): boolean {
593
+ return (model?.exposure || 'public') === 'public';
594
+ }
595
+
596
+ private isQueryableModel(model?: Pick<RegisteredModelSchema, 'abstract'>): boolean {
597
+ return !!model && model.abstract !== true;
598
+ }
599
+
600
+ getSuggestedUses(use: string): string[] {
601
+ const model = this.modelSchemas.get(String(use || '').trim());
602
+ if (model?.suggestedUses?.length) {
603
+ return normalizeStringArray(model.suggestedUses);
604
+ }
605
+ return this.listModelUses({ directUseOnly: true })
606
+ .filter((item) => item !== use)
607
+ .slice(0, 20);
608
+ }
609
+
610
+ hasQueryableModel(use: string): boolean {
611
+ const model = this.modelSchemas.get(String(use || '').trim());
612
+ return this.isQueryableModel(model);
613
+ }
614
+
615
+ isDirectUseAllowed(use: string): boolean {
616
+ const model = this.modelSchemas.get(String(use || '').trim());
617
+ return model ? this.isQueryableModel(model) && model.allowDirectUse !== false : true;
618
+ }
619
+
620
+ listPublicTreeRoots(): string[] {
621
+ return Array.from(this.publicTreeRoots).sort();
622
+ }
623
+
624
+ listModelUses(options: { publicOnly?: boolean; directUseOnly?: boolean; queryableOnly?: boolean } = {}): string[] {
625
+ const { publicOnly = false, directUseOnly = false, queryableOnly = false } = options;
626
+ return Array.from(this.modelSchemas.values())
627
+ .filter((model) => !publicOnly || (this.isPublicModel(model) && this.isQueryableModel(model)))
628
+ .filter((model) => !queryableOnly || this.isQueryableModel(model))
629
+ .filter((model) => !directUseOnly || (this.isQueryableModel(model) && model.allowDirectUse !== false))
630
+ .map((model) => model.use)
631
+ .sort();
632
+ }
633
+
634
+ getSchemaBundle(uses?: string[]): FlowSchemaBundleDocument {
635
+ return {
636
+ items: (Array.isArray(uses) && uses.length > 0 ? uses.filter((use) => this.hasQueryableModel(use)) : []).map(
637
+ (use) => this.buildBundleNode(use, [], new Set<string>()),
638
+ ),
639
+ };
640
+ }
641
+
642
+ getModelDocument(use: string, contextChain: FlowSchemaContextEdge[] = []): FlowSchemaDocument {
643
+ return _.cloneDeep(this.getModelDocumentRef(use, contextChain));
644
+ }
645
+
646
+ getModelDocumentRef(use: string, contextChain: FlowSchemaContextEdge[] = []): Readonly<FlowSchemaDocument> {
647
+ const cacheKey = this.createContextVisitKey(use, contextChain);
648
+ const cached = this.modelDocumentCache.get(cacheKey);
649
+ if (cached) {
650
+ return cached;
651
+ }
652
+ const document = this.buildModelDocument(use, contextChain, new Set<string>());
653
+ const frozen = deepFreezePlainGraph(document);
654
+ this.modelDocumentCache.set(cacheKey, frozen);
655
+ return frozen;
656
+ }
657
+
658
+ getPublicModelDocument(
659
+ use: string,
660
+ options: { detail?: FlowSchemaDetail; contextChain?: FlowSchemaContextEdge[] } = {},
661
+ ): FlowSchemaPublicDocument {
662
+ return _.cloneDeep(this.getPublicModelDocumentRef(use, options));
663
+ }
664
+
665
+ getPublicModelDocumentRef(
666
+ use: string,
667
+ options: { detail?: FlowSchemaDetail; contextChain?: FlowSchemaContextEdge[] } = {},
668
+ ): Readonly<FlowSchemaPublicDocument> {
669
+ const detail = options.detail === 'full' ? 'full' : 'compact';
670
+ const contextChain = options.contextChain || [];
671
+ const cacheKey = `${detail}::${this.createContextVisitKey(use, contextChain)}`;
672
+ const cached = this.publicModelDocumentCache.get(cacheKey);
673
+ if (cached) {
674
+ return cached;
675
+ }
676
+
677
+ const document =
678
+ detail === 'full'
679
+ ? this.buildFullPublicModelDocument(use, contextChain)
680
+ : this.buildCompactPublicModelDocument(use, contextChain);
681
+ const frozen = deepFreezePlainGraph(document);
682
+ this.publicModelDocumentCache.set(cacheKey, frozen);
683
+ return frozen;
684
+ }
685
+
686
+ private buildFullPublicModelDocument(use: string, contextChain: FlowSchemaContextEdge[]): FlowSchemaPublicDocument {
687
+ const document = this.getModelDocumentRef(use, contextChain);
688
+ return {
689
+ use: document.use,
690
+ title: document.title,
691
+ jsonSchema: document.jsonSchema,
692
+ dynamicHints: document.dynamicHints,
693
+ minimalExample: document.minimalExample,
694
+ commonPatterns: document.commonPatterns,
695
+ antiPatterns: document.antiPatterns,
696
+ hash: document.hash,
697
+ source: document.source,
698
+ };
699
+ }
700
+
701
+ private buildCompactPublicModelDocument(
702
+ use: string,
703
+ contextChain: FlowSchemaContextEdge[],
704
+ ): FlowSchemaPublicDocument {
705
+ const resolved = this.resolveModelSchemaRef(use, contextChain);
706
+ const jsonSchema = this.buildCompactModelSnapshotSchemaRef(use, contextChain);
707
+ const minimalExample = _.cloneDeep(
708
+ resolved?.docs?.minimalExample ??
709
+ resolved?.examples?.[0] ??
710
+ resolved?.skeleton ??
711
+ buildSkeletonFromSchema(jsonSchema),
712
+ );
713
+
714
+ return {
715
+ use,
716
+ title: resolved?.title || use,
717
+ jsonSchema,
718
+ dynamicHints: [...this.getModelLocalDynamicHintsRef(use, contextChain)],
719
+ minimalExample,
720
+ commonPatterns: _.cloneDeep(resolved?.docs?.commonPatterns || []),
721
+ antiPatterns: _.cloneDeep(resolved?.docs?.antiPatterns || []),
722
+ hash: this.getCompactModelSchemaHash(use, contextChain),
723
+ source: (resolved?.coverage || { source: 'third-party' as const }).source,
724
+ };
725
+ }
726
+
727
+ private getModelLocalDynamicHintsRef(
728
+ use: string,
729
+ contextChain: FlowSchemaContextEdge[] = [],
730
+ ): Readonly<FlowDynamicHint[]> {
731
+ const cacheKey = this.createContextVisitKey(use, contextChain);
732
+ const cached = this.modelLocalDynamicHintsCache.get(cacheKey);
733
+ if (cached) {
734
+ return cached;
735
+ }
736
+
737
+ const hints = this.buildModelLocalDynamicHints(use, contextChain);
738
+ const frozen = deepFreezePlainGraph(hints);
739
+ this.modelLocalDynamicHintsCache.set(cacheKey, frozen);
740
+ return frozen;
741
+ }
742
+
743
+ private buildModelLocalDynamicHints(use: string, contextChain: FlowSchemaContextEdge[]): FlowDynamicHint[] {
744
+ const resolved = this.resolveModelSchemaRef(use, contextChain);
745
+ const baseCoverage = resolved.coverage || { status: 'unresolved', source: 'third-party' as const };
746
+ const flowDiagnostics = this.collectFlowSchemaDiagnostics(use);
747
+ const slotHints = Object.entries(resolved?.subModelSlots || {}).map(([slotKey, slot]) => {
748
+ const allowedUses = this.resolveSlotAllowedUses(use, slotKey, slot);
749
+ return createFlowHint(
750
+ {
751
+ kind: slot.dynamic || slot.fieldBindingContext ? 'dynamic-children' : 'manual-schema-required',
752
+ path: `${use}.subModels.${slotKey}`,
753
+ message:
754
+ slot.description ||
755
+ `${use}.subModels.${slotKey} accepts ${slot.type}${
756
+ allowedUses.length ? `: ${allowedUses.join(', ')}` : ''
757
+ }.`,
758
+ },
759
+ {
760
+ slotRules: {
761
+ slotKey,
762
+ type: slot.type,
763
+ allowedUses,
764
+ },
765
+ },
766
+ );
767
+ });
768
+ const coverage = this.buildDocumentCoverage(baseCoverage, flowDiagnostics.statuses);
769
+
770
+ return normalizeSchemaHints([
771
+ ...(resolved?.dynamicHints || []),
772
+ ...slotHints,
773
+ ...flowDiagnostics.hints,
774
+ ...(coverage.status === 'unresolved'
775
+ ? [
776
+ createFlowHint(
777
+ {
778
+ kind: 'unresolved-model' as const,
779
+ path: use,
780
+ message: `${use} has no registered server-safe schema contribution yet.`,
781
+ },
782
+ {
783
+ unresolvedReason: 'missing-model-contribution',
784
+ recommendedFallback: { use },
785
+ },
786
+ ),
787
+ ]
788
+ : []),
789
+ ]);
790
+ }
791
+
792
+ getModelSchemaHash(use: string, contextChain: FlowSchemaContextEdge[] = []): string {
793
+ const cacheKey = this.createContextVisitKey(use, contextChain);
794
+ const cached = this.modelSchemaHashCache.get(cacheKey);
795
+ if (cached) {
796
+ return cached;
797
+ }
798
+ const hash = hashString(stableStringify(this.buildModelSnapshotSchemaRef(use, contextChain)));
799
+ this.modelSchemaHashCache.set(cacheKey, hash);
800
+ return hash;
801
+ }
802
+
803
+ getCompactModelSchemaHash(use: string, contextChain: FlowSchemaContextEdge[] = []): string {
804
+ const cacheKey = this.createContextVisitKey(use, contextChain);
805
+ const cached = this.compactModelSchemaHashCache.get(cacheKey);
806
+ if (cached) {
807
+ return cached;
808
+ }
809
+ const hash = hashString(stableStringify(this.buildCompactModelSnapshotSchemaRef(use, contextChain)));
810
+ this.compactModelSchemaHashCache.set(cacheKey, hash);
811
+ return hash;
812
+ }
813
+
814
+ private createSlotUseExpansionKey(parentUse: string, slotKey: string): string {
815
+ return `${parentUse}::${slotKey}`;
816
+ }
817
+
818
+ private getSlotUseExpansionUses(parentUse: string, slotKey: string): string[] {
819
+ return this.slotUseExpansions.get(this.createSlotUseExpansionKey(parentUse, slotKey)) || [];
820
+ }
821
+
822
+ resolveSlotAllowedUses(parentUse: string, slotKey: string, slot?: FlowSubModelSlotSchema): string[] {
823
+ if (!slot) {
824
+ return [];
825
+ }
826
+
827
+ if (slot.fieldBindingContext) {
828
+ return _.uniq(this.resolveFieldBindingCandidates(slot.fieldBindingContext).map((candidate) => candidate.use));
829
+ }
830
+
831
+ return _.uniq([...collectAllowedUses(slot), ...this.getSlotUseExpansionUses(parentUse, slotKey)]);
832
+ }
833
+
834
+ private buildModelDocument(
835
+ use: string,
836
+ contextChain: FlowSchemaContextEdge[],
837
+ visited: Set<string>,
838
+ ): FlowSchemaDocument {
839
+ const resolved = this.resolveModelSchemaRef(use, contextChain);
840
+ const baseCoverage = resolved.coverage || { status: 'unresolved', source: 'third-party' as const };
841
+ const flowDiagnostics = this.collectFlowSchemaDiagnostics(use);
842
+ const coverage = this.buildDocumentCoverage(baseCoverage, flowDiagnostics.statuses);
843
+ const dynamicHints = normalizeSchemaHints([
844
+ ...this.getModelLocalDynamicHintsRef(use, contextChain),
845
+ ...this.collectNestedDocumentHints(use, contextChain, visited),
846
+ ]);
847
+
848
+ const jsonSchema = this.buildModelSnapshotSchemaRef(use, contextChain);
849
+ const skeleton = _.cloneDeep(resolved?.skeleton ?? buildSkeletonFromSchema(jsonSchema));
850
+ const minimalExample = _.cloneDeep(resolved?.docs?.minimalExample ?? resolved?.examples?.[0] ?? skeleton);
851
+ const hash = this.getModelSchemaHash(use, contextChain);
852
+
853
+ return {
854
+ use,
855
+ title: resolved?.title || use,
856
+ jsonSchema,
857
+ coverage,
858
+ dynamicHints,
859
+ examples: resolved?.examples || [],
860
+ minimalExample,
861
+ commonPatterns: _.cloneDeep(resolved?.docs?.commonPatterns || []),
862
+ antiPatterns: _.cloneDeep(resolved?.docs?.antiPatterns || []),
863
+ skeleton,
864
+ hash,
865
+ source: coverage.source,
866
+ };
867
+ }
868
+
869
+ private buildBundleNode(
870
+ use: string,
871
+ contextChain: FlowSchemaContextEdge[],
872
+ visited: Set<string>,
873
+ compatibility?: FlowFieldModelCompatibility,
874
+ ): FlowSchemaBundleNode {
875
+ const resolved = this.resolveModelSchemaRef(use, contextChain);
876
+ const visitKey = this.createContextVisitKey(use, contextChain);
877
+ const node: FlowSchemaBundleNode = {
878
+ use,
879
+ title: resolved?.title || use,
880
+ };
881
+
882
+ if (compatibility) {
883
+ node.compatibility = _.cloneDeep(compatibility);
884
+ }
885
+
886
+ if (visited.has(visitKey) || this.isContextCycle(use, contextChain)) {
887
+ return node;
888
+ }
889
+
890
+ visited.add(visitKey);
891
+ try {
892
+ const subModelCatalog = this.buildBundleSubModelCatalog(use, resolved?.subModelSlots, contextChain, visited);
893
+ if (subModelCatalog && Object.keys(subModelCatalog).length > 0) {
894
+ node.subModelCatalog = subModelCatalog;
895
+ }
896
+ return node;
897
+ } finally {
898
+ visited.delete(visitKey);
899
+ }
900
+ }
901
+
902
+ private buildBundleSubModelCatalog(
903
+ parentUse: string,
904
+ slots: Record<string, FlowSubModelSlotSchema> | undefined,
905
+ contextChain: FlowSchemaContextEdge[],
906
+ visited: Set<string>,
907
+ ): Record<string, FlowSchemaBundleSlotCatalog> | undefined {
908
+ if (!slots || Object.keys(slots).length === 0) {
909
+ return undefined;
910
+ }
911
+
912
+ const entries = Object.entries(slots).map(([slotKey, slot]) => {
913
+ const edgeBase = {
914
+ parentUse,
915
+ slotKey,
916
+ };
917
+ const fieldBindingCandidates = slot.fieldBindingContext
918
+ ? this.resolveFieldBindingCandidates(slot.fieldBindingContext)
919
+ : [];
920
+ const allowedUses =
921
+ fieldBindingCandidates.length > 0
922
+ ? _.uniq(fieldBindingCandidates.map((item) => item.use))
923
+ : this.resolveSlotAllowedUses(parentUse, slotKey, slot);
924
+ const catalog: FlowSchemaBundleSlotCatalog = {
925
+ type: slot.type,
926
+ candidates:
927
+ fieldBindingCandidates.length > 0
928
+ ? fieldBindingCandidates.map((candidate) =>
929
+ this.buildBundleNode(
930
+ candidate.use,
931
+ [
932
+ ...contextChain,
933
+ {
934
+ ...edgeBase,
935
+ childUse: candidate.use,
936
+ },
937
+ ],
938
+ visited,
939
+ candidate.compatibility,
940
+ ),
941
+ )
942
+ : allowedUses.map((childUse) =>
943
+ this.buildBundleNode(
944
+ childUse,
945
+ [
946
+ ...contextChain,
947
+ {
948
+ ...edgeBase,
949
+ childUse,
950
+ },
951
+ ],
952
+ visited,
953
+ ),
954
+ ),
955
+ };
956
+
957
+ if (slot.required !== undefined) {
958
+ catalog.required = slot.required;
959
+ }
960
+
961
+ if (slot.type === 'array' && typeof slot.minItems === 'number') {
962
+ catalog.minItems = slot.minItems;
963
+ }
964
+
965
+ if (allowedUses.length === 0) {
966
+ catalog.open = true;
967
+ }
968
+
969
+ return [slotKey, catalog] as const;
970
+ });
971
+
972
+ return entries.length ? Object.fromEntries(entries) : undefined;
973
+ }
974
+
975
+ private createContextVisitKey(use: string, contextChain: FlowSchemaContextEdge[]): string {
976
+ return `${use}::${stableStringify(contextChain)}`;
977
+ }
978
+
979
+ private isContextCycle(use: string, contextChain: FlowSchemaContextEdge[]): boolean {
980
+ if (!contextChain.length) {
981
+ return false;
982
+ }
983
+
984
+ const ancestorUses = [contextChain[0].parentUse, ...contextChain.slice(0, -1).map((edge) => edge.childUse)];
985
+ return ancestorUses.includes(use);
986
+ }
987
+
988
+ buildModelSnapshotSchema(use: string, contextChain: FlowSchemaContextEdge[] = []): FlowJsonSchema {
989
+ return _.cloneDeep(this.buildModelSnapshotSchemaRef(use, contextChain));
990
+ }
991
+
992
+ buildCompactModelSnapshotSchema(use: string, contextChain: FlowSchemaContextEdge[] = []): FlowJsonSchema {
993
+ return _.cloneDeep(this.buildCompactModelSnapshotSchemaRef(use, contextChain));
994
+ }
995
+
996
+ private buildModelSnapshotSchemaRef(use: string, contextChain: FlowSchemaContextEdge[] = []): FlowJsonSchema {
997
+ const cacheKey = this.createContextVisitKey(use, contextChain);
998
+ const cached = this.modelSnapshotSchemaCache.get(cacheKey);
999
+ if (cached) {
1000
+ return cached;
1001
+ }
1002
+ const schema = this.buildModelSnapshotSchemaInternal(use, contextChain, new Set<string>());
1003
+ const frozen = deepFreezePlainGraph(schema);
1004
+ this.modelSnapshotSchemaCache.set(cacheKey, frozen);
1005
+ return frozen;
1006
+ }
1007
+
1008
+ private buildCompactModelSnapshotSchemaRef(use: string, contextChain: FlowSchemaContextEdge[] = []): FlowJsonSchema {
1009
+ const cacheKey = this.createContextVisitKey(use, contextChain);
1010
+ const cached = this.compactModelSnapshotSchemaCache.get(cacheKey);
1011
+ if (cached) {
1012
+ return cached;
1013
+ }
1014
+ const schema = this.buildCompactModelSnapshotSchemaInternal(use, contextChain);
1015
+ const frozen = deepFreezePlainGraph(schema);
1016
+ this.compactModelSnapshotSchemaCache.set(cacheKey, frozen);
1017
+ return frozen;
1018
+ }
1019
+
1020
+ private buildCompactModelSnapshotSchemaInternal(use: string, contextChain: FlowSchemaContextEdge[]): FlowJsonSchema {
1021
+ const resolved = this.resolveModelSchemaRef(use, contextChain);
1022
+ const subModelsSchema = this.buildCompactSubModelsSchemaFromSlots(use, resolved?.subModelSlots, contextChain);
1023
+ return this.buildSnapshotShellSchema(use, resolved, subModelsSchema);
1024
+ }
1025
+
1026
+ private buildModelSnapshotSchemaInternal(
1027
+ use: string,
1028
+ contextChain: FlowSchemaContextEdge[],
1029
+ visited: Set<string>,
1030
+ ): FlowJsonSchema {
1031
+ const resolved = this.resolveModelSchemaRef(use, contextChain);
1032
+ const visitKey = this.createContextVisitKey(use, contextChain);
1033
+
1034
+ if (visited.has(visitKey) || this.isContextCycle(use, contextChain)) {
1035
+ return this.buildTruncatedSnapshotSchema(use, resolved);
1036
+ }
1037
+
1038
+ visited.add(visitKey);
1039
+ try {
1040
+ return this.buildSnapshotSchemaFromResolved(use, resolved, contextChain, visited);
1041
+ } finally {
1042
+ visited.delete(visitKey);
1043
+ }
1044
+ }
1045
+
1046
+ private buildSnapshotSchemaFromResolved(
1047
+ use: string | undefined,
1048
+ resolved: RegisteredModelSchema,
1049
+ contextChain: FlowSchemaContextEdge[],
1050
+ visited: Set<string>,
1051
+ ): FlowJsonSchema {
1052
+ const subModelsSchema = this.buildSubModelsSchemaFromSlots(
1053
+ use || '',
1054
+ resolved?.subModelSlots,
1055
+ contextChain,
1056
+ visited,
1057
+ );
1058
+ return this.buildSnapshotShellSchema(use, resolved, subModelsSchema);
1059
+ }
1060
+
1061
+ private buildSnapshotShellSchema(
1062
+ use: string | undefined,
1063
+ resolved: RegisteredModelSchema,
1064
+ subModelsSchema: FlowJsonSchema,
1065
+ ): FlowJsonSchema {
1066
+ const flowRegistrySchema = resolved?.flowRegistrySchema || { type: 'object', additionalProperties: true };
1067
+ const stepParamsSchema = resolved?.stepParamsSchema || { type: 'object', additionalProperties: true };
1068
+ return {
1069
+ $schema: JSON_SCHEMA_DRAFT_07,
1070
+ type: 'object',
1071
+ properties: {
1072
+ uid: { type: 'string' },
1073
+ use: use ? { const: use } : { type: 'string' },
1074
+ async: { type: 'boolean' },
1075
+ parentId: { type: 'string' },
1076
+ subKey: { type: 'string' },
1077
+ subType: { type: 'string', enum: ['object', 'array'] },
1078
+ sortIndex: { type: 'number' },
1079
+ stepParams: stepParamsSchema,
1080
+ flowRegistry: mergeSchemas(flowRegistrySchema, resolved?.flowRegistrySchemaPatch) || {
1081
+ type: 'object',
1082
+ additionalProperties: true,
1083
+ },
1084
+ subModels: subModelsSchema,
1085
+ },
1086
+ required: ['uid', 'use'],
1087
+ additionalProperties: true,
1088
+ };
1089
+ }
1090
+
1091
+ private buildTruncatedSnapshotSchema(use: string | undefined, resolved: RegisteredModelSchema): FlowJsonSchema {
1092
+ return this.buildSnapshotShellSchema(use, resolved, {
1093
+ type: 'object',
1094
+ additionalProperties: true,
1095
+ });
1096
+ }
1097
+
1098
+ buildStaticFlowRegistrySchema(use: string, contextChain: FlowSchemaContextEdge[] = []): FlowJsonSchema {
1099
+ const resolved = this.resolveModelSchemaRef(use, contextChain);
1100
+ const schema = mergeSchemas(resolved?.flowRegistrySchema, resolved?.flowRegistrySchemaPatch);
1101
+ if (schema) {
1102
+ return _.cloneDeep(schema);
1103
+ }
1104
+ return { type: 'object', additionalProperties: true };
1105
+ }
1106
+
1107
+ buildStaticStepParamsSchema(use: string, contextChain: FlowSchemaContextEdge[] = []): FlowJsonSchema {
1108
+ const resolved = this.resolveModelSchemaRef(use, contextChain);
1109
+ if (resolved?.stepParamsSchema) {
1110
+ return _.cloneDeep(resolved.stepParamsSchema);
1111
+ }
1112
+ return { type: 'object', additionalProperties: true };
1113
+ }
1114
+
1115
+ buildSubModelsSchema(use: string, contextChain: FlowSchemaContextEdge[] = []): FlowJsonSchema {
1116
+ const resolved = this.resolveModelSchemaRef(use, contextChain);
1117
+ return this.buildSubModelsSchemaFromSlots(use, resolved?.subModelSlots, contextChain, new Set<string>());
1118
+ }
1119
+
1120
+ private buildCompactSubModelsSchemaFromSlots(
1121
+ parentUse: string,
1122
+ slots: Record<string, FlowSubModelSlotSchema> | undefined,
1123
+ contextChain: FlowSchemaContextEdge[] = [],
1124
+ ): FlowJsonSchema {
1125
+ if (!slots || Object.keys(slots).length === 0) {
1126
+ return { type: 'object', additionalProperties: true };
1127
+ }
1128
+
1129
+ const properties: Record<string, FlowJsonSchema> = {};
1130
+ const required: string[] = [];
1131
+ for (const [slotKey, slot] of Object.entries(slots)) {
1132
+ const itemSchema = this.buildCompactSlotTargetSchema(parentUse, slotKey, slot, contextChain);
1133
+ properties[slotKey] =
1134
+ slot.type === 'array'
1135
+ ? {
1136
+ type: 'array',
1137
+ ...(typeof slot.minItems === 'number' ? { minItems: slot.minItems } : {}),
1138
+ items: itemSchema,
1139
+ }
1140
+ : itemSchema;
1141
+ if (slot.required) {
1142
+ required.push(slotKey);
1143
+ }
1144
+ }
1145
+
1146
+ return {
1147
+ type: 'object',
1148
+ properties,
1149
+ ...(required.length ? { required } : {}),
1150
+ additionalProperties: true,
1151
+ };
1152
+ }
1153
+
1154
+ private buildSubModelsSchemaFromSlots(
1155
+ parentUse: string,
1156
+ slots: Record<string, FlowSubModelSlotSchema> | undefined,
1157
+ contextChain: FlowSchemaContextEdge[] = [],
1158
+ visited: Set<string>,
1159
+ ): FlowJsonSchema {
1160
+ if (!slots || Object.keys(slots).length === 0) {
1161
+ return { type: 'object', additionalProperties: true };
1162
+ }
1163
+ const properties: Record<string, FlowJsonSchema> = {};
1164
+ const required: string[] = [];
1165
+ for (const [slotKey, slot] of Object.entries(slots)) {
1166
+ const itemSchema = this.buildSlotTargetSchema(parentUse, slotKey, slot, contextChain, visited);
1167
+ properties[slotKey] =
1168
+ slot.type === 'array'
1169
+ ? {
1170
+ type: 'array',
1171
+ ...(typeof slot.minItems === 'number' ? { minItems: slot.minItems } : {}),
1172
+ items: itemSchema,
1173
+ }
1174
+ : itemSchema;
1175
+ if (slot.required) {
1176
+ required.push(slotKey);
1177
+ }
1178
+ }
1179
+ return {
1180
+ type: 'object',
1181
+ properties,
1182
+ ...(required.length ? { required } : {}),
1183
+ additionalProperties: true,
1184
+ };
1185
+ }
1186
+
1187
+ private buildInferredFlowRegistrySchema(use: string): FlowJsonSchema | undefined {
1188
+ const registered = this.modelSchemas.get(use);
1189
+ const modelClass = registered?.modelClass as any;
1190
+ const flowsMap = modelClass?.globalFlowRegistry?.getFlows?.() as Map<string, any> | undefined;
1191
+ if (!flowsMap?.size) {
1192
+ return undefined;
1193
+ }
1194
+
1195
+ const properties: Record<string, FlowJsonSchema> = {};
1196
+ for (const [flowKey, flowDef] of flowsMap.entries()) {
1197
+ const stepProperties: Record<string, FlowJsonSchema> = {};
1198
+ const steps = flowDef?.steps || {};
1199
+ for (const [stepKey, stepDef] of Object.entries(steps)) {
1200
+ stepProperties[stepKey] = this.buildStepDefinitionSchema(stepDef as StepDefinition);
1201
+ }
1202
+ properties[flowKey] = {
1203
+ type: 'object',
1204
+ properties: {
1205
+ key: { type: 'string' },
1206
+ title: { type: 'string' },
1207
+ manual: { type: 'boolean' },
1208
+ sort: { type: 'number' },
1209
+ on: this.buildFlowOnSchema(),
1210
+ steps: {
1211
+ type: 'object',
1212
+ properties: stepProperties,
1213
+ additionalProperties: false,
1214
+ },
1215
+ },
1216
+ additionalProperties: true,
1217
+ };
1218
+ }
1219
+ return {
1220
+ type: 'object',
1221
+ properties,
1222
+ additionalProperties: true,
1223
+ };
1224
+ }
1225
+
1226
+ private buildInferredStepParamsSchema(use: string): FlowJsonSchema | undefined {
1227
+ const registered = this.modelSchemas.get(use);
1228
+ const modelClass = registered?.modelClass as any;
1229
+ const flowsMap = modelClass?.globalFlowRegistry?.getFlows?.() as Map<string, any> | undefined;
1230
+ if (!flowsMap?.size) {
1231
+ return undefined;
1232
+ }
1233
+
1234
+ const properties: Record<string, FlowJsonSchema> = {};
1235
+ for (const [flowKey, flowDef] of flowsMap.entries()) {
1236
+ const stepProperties: Record<string, FlowJsonSchema> = {};
1237
+ const steps = flowDef?.steps || {};
1238
+ for (const [stepKey, stepDef] of Object.entries(steps)) {
1239
+ const resolved = this.resolveStepParamsSchema(stepDef as StepDefinition, `${use}.${flowKey}.${stepKey}`);
1240
+ stepProperties[stepKey] = resolved.schema || { type: 'object', additionalProperties: true };
1241
+ }
1242
+ properties[flowKey] = {
1243
+ type: 'object',
1244
+ properties: stepProperties,
1245
+ additionalProperties: false,
1246
+ };
1247
+ }
1248
+ return {
1249
+ type: 'object',
1250
+ properties,
1251
+ additionalProperties: true,
1252
+ };
1253
+ }
1254
+
1255
+ buildFlowOnSchema(): FlowJsonSchema {
1256
+ return {
1257
+ anyOf: [
1258
+ { type: 'string' },
1259
+ {
1260
+ type: 'object',
1261
+ properties: {
1262
+ eventName: { type: 'string' },
1263
+ defaultParams: { type: 'object', additionalProperties: true },
1264
+ phase: {
1265
+ type: 'string',
1266
+ enum: ['beforeAllFlows', 'afterAllFlows', 'beforeFlow', 'afterFlow', 'beforeStep', 'afterStep'],
1267
+ },
1268
+ flowKey: { type: 'string' },
1269
+ stepKey: { type: 'string' },
1270
+ },
1271
+ required: ['eventName'],
1272
+ additionalProperties: true,
1273
+ },
1274
+ ],
1275
+ };
1276
+ }
1277
+
1278
+ buildStepDefinitionSchema(step: StepDefinition): FlowJsonSchema {
1279
+ return {
1280
+ type: 'object',
1281
+ properties: {
1282
+ key: { type: 'string' },
1283
+ flowKey: { type: 'string' },
1284
+ title: { type: 'string' },
1285
+ use: { type: 'string' },
1286
+ sort: { type: 'number' },
1287
+ preset: { type: 'boolean' },
1288
+ isAwait: { type: 'boolean' },
1289
+ manual: { type: 'boolean' },
1290
+ on: this.buildFlowOnSchema(),
1291
+ defaultParams: { type: ['object', 'array', 'string', 'number', 'boolean', 'null'] as any },
1292
+ paramsSchemaOverride: { type: 'object', additionalProperties: true },
1293
+ },
1294
+ additionalProperties: true,
1295
+ };
1296
+ }
1297
+
1298
+ resolveStepParamsSchema(step: StepDefinition, path: string): StepSchemaResolution {
1299
+ if (step.paramsSchemaOverride) {
1300
+ return {
1301
+ schema: _.cloneDeep(step.paramsSchemaOverride),
1302
+ hints: normalizeSchemaHints(step.schemaDocs?.dynamicHints),
1303
+ coverage: 'manual',
1304
+ };
1305
+ }
1306
+
1307
+ if (step.use) {
1308
+ const action = this.getAction(step.use);
1309
+ if (action?.schema) {
1310
+ return {
1311
+ schema: _.cloneDeep(action.schema),
1312
+ hints: normalizeSchemaHints([...(action.dynamicHints || []), ...(step.schemaDocs?.dynamicHints || [])]),
1313
+ coverage:
1314
+ action.coverage.status === 'auto' || action.coverage.status === 'manual'
1315
+ ? action.coverage.status
1316
+ : action.coverage.status === 'unresolved'
1317
+ ? 'unresolved'
1318
+ : 'mixed',
1319
+ };
1320
+ }
1321
+ return {
1322
+ schema: { type: 'object', additionalProperties: true },
1323
+ hints: [
1324
+ createFlowHint(
1325
+ {
1326
+ kind: 'unresolved-action',
1327
+ path,
1328
+ message: `${path} references unresolved action "${step.use}".`,
1329
+ },
1330
+ {
1331
+ unresolvedReason: 'missing-action-contribution',
1332
+ recommendedFallback: { use: step.use, params: {} },
1333
+ },
1334
+ ),
1335
+ ...(step.schemaDocs?.dynamicHints || []),
1336
+ ],
1337
+ coverage: 'unresolved',
1338
+ };
1339
+ }
1340
+
1341
+ const inferred = inferParamsSchemaFromUiSchema(path, step.uiSchema as any, path);
1342
+ return {
1343
+ schema: mergeSchemas(inferred.schema, step.paramsSchemaPatch),
1344
+ hints: normalizeSchemaHints([...(inferred.hints || []), ...(step.schemaDocs?.dynamicHints || [])]),
1345
+ coverage: step.paramsSchemaPatch ? 'mixed' : inferred.coverage,
1346
+ };
1347
+ }
1348
+
1349
+ private buildSlotTargetSchema(
1350
+ parentUse: string,
1351
+ slotKey: string,
1352
+ slot: FlowSubModelSlotSchema,
1353
+ contextChain: FlowSchemaContextEdge[],
1354
+ visited: Set<string>,
1355
+ ): FlowJsonSchema {
1356
+ if (slot.fieldBindingContext) {
1357
+ const candidateUses = _.uniq(
1358
+ this.resolveFieldBindingCandidates(slot.fieldBindingContext).map((item) => item.use),
1359
+ );
1360
+ if (candidateUses.length > 0) {
1361
+ const candidateSchemas = candidateUses.map((use) =>
1362
+ this.buildModelSnapshotSchemaInternal(
1363
+ use,
1364
+ [
1365
+ ...contextChain,
1366
+ {
1367
+ parentUse,
1368
+ slotKey,
1369
+ childUse: use,
1370
+ },
1371
+ ],
1372
+ visited,
1373
+ ),
1374
+ );
1375
+
1376
+ if (slot.schema) {
1377
+ return {
1378
+ anyOf: [...candidateSchemas, _.cloneDeep(slot.schema)],
1379
+ };
1380
+ }
1381
+
1382
+ return candidateSchemas.length === 1 ? candidateSchemas[0] : { oneOf: candidateSchemas };
1383
+ }
1384
+ }
1385
+
1386
+ const allowedUses = this.resolveSlotAllowedUses(parentUse, slotKey, slot);
1387
+ if (allowedUses.length > 0) {
1388
+ const knownSchemas = allowedUses.map((use) =>
1389
+ this.buildModelSnapshotSchemaInternal(
1390
+ use,
1391
+ [
1392
+ ...contextChain,
1393
+ {
1394
+ parentUse,
1395
+ slotKey,
1396
+ childUse: use,
1397
+ },
1398
+ ],
1399
+ visited,
1400
+ ),
1401
+ );
1402
+ if (slot.schema) {
1403
+ return {
1404
+ anyOf: [...knownSchemas, _.cloneDeep(slot.schema)],
1405
+ };
1406
+ }
1407
+ if (knownSchemas.length === 1) {
1408
+ return knownSchemas[0];
1409
+ }
1410
+ return {
1411
+ oneOf: knownSchemas,
1412
+ };
1413
+ }
1414
+ if (slot.childSchemaPatch || slot.descendantSchemaPatches?.length) {
1415
+ return this.buildAnonymousSlotSnapshotSchema(parentUse, slotKey, slot, contextChain, visited);
1416
+ }
1417
+ if (slot.schema && !slot.childSchemaPatch && !slot.descendantSchemaPatches?.length) {
1418
+ return _.cloneDeep(slot.schema);
1419
+ }
1420
+ return { type: 'object', additionalProperties: true };
1421
+ }
1422
+
1423
+ private buildCompactSlotTargetSchema(
1424
+ parentUse: string,
1425
+ slotKey: string,
1426
+ slot: FlowSubModelSlotSchema,
1427
+ contextChain: FlowSchemaContextEdge[],
1428
+ ): FlowJsonSchema {
1429
+ if (slot.fieldBindingContext) {
1430
+ const candidateUses = _.uniq(
1431
+ this.resolveFieldBindingCandidates(slot.fieldBindingContext).map((item) => item.use),
1432
+ );
1433
+ if (candidateUses.length > 0) {
1434
+ const candidateSchemas = candidateUses.map((use) =>
1435
+ this.buildCompactSlotCandidateSchema(use, [
1436
+ ...contextChain,
1437
+ {
1438
+ parentUse,
1439
+ slotKey,
1440
+ childUse: use,
1441
+ },
1442
+ ]),
1443
+ );
1444
+
1445
+ if (slot.schema) {
1446
+ return {
1447
+ anyOf: [...candidateSchemas, _.cloneDeep(slot.schema)],
1448
+ };
1449
+ }
1450
+
1451
+ return candidateSchemas.length === 1 ? candidateSchemas[0] : { oneOf: candidateSchemas };
1452
+ }
1453
+ }
1454
+
1455
+ const allowedUses = this.resolveSlotAllowedUses(parentUse, slotKey, slot);
1456
+ if (allowedUses.length > 0) {
1457
+ const knownSchemas = allowedUses.map((use) =>
1458
+ this.buildCompactSlotCandidateSchema(use, [
1459
+ ...contextChain,
1460
+ {
1461
+ parentUse,
1462
+ slotKey,
1463
+ childUse: use,
1464
+ },
1465
+ ]),
1466
+ );
1467
+
1468
+ if (slot.schema) {
1469
+ return {
1470
+ anyOf: [...knownSchemas, _.cloneDeep(slot.schema)],
1471
+ };
1472
+ }
1473
+
1474
+ return knownSchemas.length === 1 ? knownSchemas[0] : { oneOf: knownSchemas };
1475
+ }
1476
+
1477
+ if (slot.childSchemaPatch || slot.descendantSchemaPatches?.length) {
1478
+ return this.buildCompactAnonymousSlotSnapshotSchema(parentUse, slotKey, slot);
1479
+ }
1480
+
1481
+ if (slot.schema && !slot.childSchemaPatch && !slot.descendantSchemaPatches?.length) {
1482
+ return _.cloneDeep(slot.schema);
1483
+ }
1484
+
1485
+ return { type: 'object', additionalProperties: true };
1486
+ }
1487
+
1488
+ private buildCompactSlotCandidateSchema(use: string, contextChain: FlowSchemaContextEdge[]): FlowJsonSchema {
1489
+ const resolved = this.resolveModelSchemaRef(use, contextChain);
1490
+ return this.buildTruncatedSnapshotSchema(use, resolved);
1491
+ }
1492
+
1493
+ private buildCompactAnonymousSlotSnapshotSchema(
1494
+ parentUse: string,
1495
+ slotKey: string,
1496
+ slot: FlowSubModelSlotSchema,
1497
+ ): FlowJsonSchema {
1498
+ const anonymousResolved = this.createAnonymousResolvedSchema(parentUse, slotKey, slot);
1499
+ return this.buildTruncatedSnapshotSchema(undefined, anonymousResolved);
1500
+ }
1501
+
1502
+ private buildAnonymousSlotSnapshotSchema(
1503
+ parentUse: string,
1504
+ slotKey: string,
1505
+ slot: FlowSubModelSlotSchema,
1506
+ contextChain: FlowSchemaContextEdge[],
1507
+ visited: Set<string>,
1508
+ ): FlowJsonSchema {
1509
+ const anonymousResolved = this.createAnonymousResolvedSchema(parentUse, slotKey, slot);
1510
+ return this.buildSnapshotSchemaFromResolved(
1511
+ '',
1512
+ anonymousResolved,
1513
+ [
1514
+ ...contextChain,
1515
+ {
1516
+ parentUse,
1517
+ slotKey,
1518
+ childUse: '',
1519
+ },
1520
+ ],
1521
+ visited,
1522
+ );
1523
+ }
1524
+
1525
+ private createAnonymousResolvedSchema(
1526
+ parentUse: string,
1527
+ slotKey: string,
1528
+ slot: FlowSubModelSlotSchema,
1529
+ ): RegisteredModelSchema {
1530
+ const anonymousResolved: RegisteredModelSchema = {
1531
+ use: '',
1532
+ title: slot.description || `${parentUse || 'AnonymousModel'}.${slotKey}`,
1533
+ examples: [],
1534
+ docs: normalizeSchemaDocs(),
1535
+ dynamicHints: [],
1536
+ coverage: {
1537
+ status: 'unresolved',
1538
+ source: 'third-party',
1539
+ },
1540
+ exposure: 'internal',
1541
+ abstract: false,
1542
+ allowDirectUse: true,
1543
+ suggestedUses: [],
1544
+ };
1545
+
1546
+ const directPatch = resolveChildSchemaPatch(slot, '');
1547
+ if (directPatch) {
1548
+ applyModelSchemaPatch(anonymousResolved, directPatch, 'third-party');
1549
+ }
1550
+
1551
+ return anonymousResolved;
1552
+ }
1553
+
1554
+ private collectNestedDocumentHints(
1555
+ use: string,
1556
+ contextChain: FlowSchemaContextEdge[],
1557
+ visited: Set<string>,
1558
+ ): FlowDynamicHint[] {
1559
+ const visitKey = this.createContextVisitKey(use, contextChain);
1560
+ if (visited.has(visitKey) || this.isContextCycle(use, contextChain)) {
1561
+ return [];
1562
+ }
1563
+
1564
+ visited.add(visitKey);
1565
+ try {
1566
+ const resolved = this.resolveModelSchemaRef(use, contextChain);
1567
+ const hints: FlowDynamicHint[] = [];
1568
+ for (const [slotKey, slot] of Object.entries(resolved.subModelSlots || {})) {
1569
+ const childUses = this.resolveSlotAllowedUses(use, slotKey, slot);
1570
+ for (const childUse of childUses) {
1571
+ const childContext = [
1572
+ ...contextChain,
1573
+ {
1574
+ parentUse: use,
1575
+ slotKey,
1576
+ childUse,
1577
+ },
1578
+ ];
1579
+ const childDocument = this.buildModelDocument(childUse, childContext, visited);
1580
+ const basePath = `${use}.subModels.${slotKey}`;
1581
+ hints.push(...childDocument.dynamicHints.map((hint) => this.prefixNestedHint(hint, basePath, childUse)));
1582
+ }
1583
+ }
1584
+ return normalizeSchemaHints(hints);
1585
+ } finally {
1586
+ visited.delete(visitKey);
1587
+ }
1588
+ }
1589
+
1590
+ private prefixNestedHint(hint: FlowDynamicHint, basePath: string, childUse: string): FlowDynamicHint {
1591
+ if (!hint.path) {
1592
+ return {
1593
+ ...hint,
1594
+ path: basePath,
1595
+ };
1596
+ }
1597
+
1598
+ if (hint.path === childUse) {
1599
+ return {
1600
+ ...hint,
1601
+ path: basePath,
1602
+ };
1603
+ }
1604
+
1605
+ if (hint.path.startsWith(`${childUse}.`)) {
1606
+ return {
1607
+ ...hint,
1608
+ path: `${basePath}.${hint.path.slice(childUse.length + 1)}`,
1609
+ };
1610
+ }
1611
+
1612
+ return {
1613
+ ...hint,
1614
+ path: `${basePath}.${hint.path}`,
1615
+ };
1616
+ }
1617
+
1618
+ private collectContextPatches(use: string, contextChain: FlowSchemaContextEdge[]): ModelPatchContribution[] {
1619
+ if (!contextChain.length) {
1620
+ return [];
1621
+ }
1622
+
1623
+ const contributions: ModelPatchContribution[] = [];
1624
+ const targetEdgeIndex = contextChain.length - 1;
1625
+
1626
+ for (let index = 0; index < contextChain.length; index++) {
1627
+ const edge = contextChain[index];
1628
+ const parentContext = contextChain.slice(0, index);
1629
+ const parentResolved = this.resolveModelSchemaRef(edge.parentUse, parentContext);
1630
+ const slot = parentResolved.subModelSlots?.[edge.slotKey];
1631
+ if (!slot) {
1632
+ continue;
1633
+ }
1634
+
1635
+ const remainingEdges = contextChain.slice(index + 1);
1636
+ for (const patch of slot.descendantSchemaPatches || []) {
1637
+ if (matchesDescendantSchemaPatch(patch, remainingEdges)) {
1638
+ contributions.push({
1639
+ patch: patch.patch,
1640
+ source: parentResolved.coverage.source,
1641
+ strict: parentResolved.coverage.strict,
1642
+ });
1643
+ }
1644
+ }
1645
+
1646
+ if (index === targetEdgeIndex) {
1647
+ const directPatch = resolveChildSchemaPatch(slot, use);
1648
+ if (directPatch) {
1649
+ contributions.push({
1650
+ patch: directPatch,
1651
+ source: parentResolved.coverage.source,
1652
+ strict: parentResolved.coverage.strict,
1653
+ });
1654
+ }
1655
+ }
1656
+ }
1657
+
1658
+ return contributions;
1659
+ }
1660
+
1661
+ private collectModelDynamicHints(use: string, modelClass: ModelConstructor, meta: FlowModelMeta): FlowDynamicHint[] {
1662
+ const hints: FlowDynamicHint[] = [];
1663
+ if (typeof meta.children === 'function') {
1664
+ hints.push(
1665
+ createFlowHint(
1666
+ {
1667
+ kind: 'dynamic-children',
1668
+ path: `${use}.meta.children`,
1669
+ message: `${use} uses function-based children and only static slot hints are available.`,
1670
+ },
1671
+ {
1672
+ unresolvedReason: 'function-children',
1673
+ },
1674
+ ),
1675
+ );
1676
+ }
1677
+ if (typeof meta.createModelOptions === 'function') {
1678
+ hints.push(
1679
+ createFlowHint(
1680
+ {
1681
+ kind: 'dynamic-children',
1682
+ path: `${use}.meta.createModelOptions`,
1683
+ message: `${use} uses function-based createModelOptions and may need manual sub-model slot schema.`,
1684
+ },
1685
+ {
1686
+ unresolvedReason: 'function-create-model-options',
1687
+ },
1688
+ ),
1689
+ );
1690
+ }
1691
+ if (typeof (modelClass as any).defineChildren === 'function') {
1692
+ hints.push(
1693
+ createFlowHint(
1694
+ {
1695
+ kind: 'dynamic-children',
1696
+ path: `${use}.defineChildren`,
1697
+ message: `${use} defines dynamic children. Schema generation only keeps static baseline.`,
1698
+ },
1699
+ {
1700
+ unresolvedReason: 'runtime-define-children',
1701
+ },
1702
+ ),
1703
+ );
1704
+ }
1705
+ return hints;
1706
+ }
1707
+
1708
+ private collectFlowSchemaDiagnostics(use: string): {
1709
+ hints: FlowDynamicHint[];
1710
+ statuses: FlowSchemaCoverage['status'][];
1711
+ } {
1712
+ const registered = this.modelSchemas.get(use);
1713
+ const modelClass = registered?.modelClass as any;
1714
+ const flowsMap = modelClass?.globalFlowRegistry?.getFlows?.() as Map<string, any> | undefined;
1715
+ if (!flowsMap?.size) {
1716
+ return {
1717
+ hints: [],
1718
+ statuses: [],
1719
+ };
1720
+ }
1721
+
1722
+ const hints: FlowDynamicHint[] = [];
1723
+ const statuses: FlowSchemaCoverage['status'][] = [];
1724
+ for (const [flowKey, flowDef] of flowsMap.entries()) {
1725
+ for (const [stepKey, stepDef] of Object.entries(flowDef?.steps || {})) {
1726
+ const resolved = this.resolveStepParamsSchema(stepDef as StepDefinition, `${use}.${flowKey}.${stepKey}`);
1727
+ if (resolved.hints?.length) {
1728
+ hints.push(...resolved.hints);
1729
+ }
1730
+ statuses.push(resolved.coverage);
1731
+ }
1732
+ }
1733
+
1734
+ return {
1735
+ hints: normalizeSchemaHints(hints),
1736
+ statuses,
1737
+ };
1738
+ }
1739
+
1740
+ private buildDocumentCoverage(
1741
+ base: FlowSchemaCoverage,
1742
+ stepStatuses: FlowSchemaCoverage['status'][],
1743
+ ): FlowSchemaCoverage {
1744
+ const statuses = new Set<FlowSchemaCoverage['status']>([base.status, ...stepStatuses].filter(Boolean));
1745
+
1746
+ let status: FlowSchemaCoverage['status'] = base.status;
1747
+ if (statuses.size > 1) {
1748
+ statuses.delete('unresolved');
1749
+ if (statuses.size === 0) {
1750
+ status = 'unresolved';
1751
+ } else {
1752
+ status = 'mixed';
1753
+ }
1754
+ } else if (statuses.size === 1) {
1755
+ status = (Array.from(statuses)[0] || 'unresolved') as FlowSchemaCoverage['status'];
1756
+ }
1757
+
1758
+ return {
1759
+ ...base,
1760
+ status,
1761
+ };
1762
+ }
1763
+
1764
+ private inferSubModelSlotsFromModelClass(
1765
+ use: string,
1766
+ modelClass: ModelConstructor,
1767
+ ): Record<string, FlowSubModelSlotSchema> {
1768
+ const meta = (((modelClass as any).meta || {}) as FlowModelMeta).schema;
1769
+ if (meta?.subModelSlots) {
1770
+ return _.cloneDeep(meta.subModelSlots);
1771
+ }
1772
+
1773
+ const createModelOptions = ((modelClass as any).meta || {}).createModelOptions;
1774
+ if (!_.isPlainObject(createModelOptions) || !_.isPlainObject((createModelOptions as any).subModels)) {
1775
+ return {};
1776
+ }
1777
+
1778
+ const slots: Record<string, FlowSubModelSlotSchema> = {};
1779
+ for (const [slotKey, slotValue] of Object.entries((createModelOptions as any).subModels || {})) {
1780
+ if (Array.isArray(slotValue)) {
1781
+ const first = slotValue[0] as any;
1782
+ slots[slotKey] = {
1783
+ type: 'array',
1784
+ ...(typeof first?.use === 'string' ? { use: first.use } : {}),
1785
+ };
1786
+ } else if (_.isPlainObject(slotValue)) {
1787
+ slots[slotKey] = {
1788
+ type: 'object',
1789
+ ...((slotValue as any).use ? { use: (slotValue as any).use } : {}),
1790
+ };
1791
+ } else {
1792
+ slots[slotKey] = {
1793
+ type: 'object',
1794
+ };
1795
+ }
1796
+ }
1797
+ return slots;
1798
+ }
1799
+ }