@rebasepro/studio 0.0.1-canary.09e5ec5

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 (361) hide show
  1. package/LICENSE +114 -0
  2. package/README.md +159 -0
  3. package/dist/ApiExplorer-gMJt5JrS.js +1053 -0
  4. package/dist/ApiExplorer-gMJt5JrS.js.map +1 -0
  5. package/dist/AuthSimulationSelector-BF4rkRGp.js +118 -0
  6. package/dist/AuthSimulationSelector-BF4rkRGp.js.map +1 -0
  7. package/dist/BranchesView-DcHZtvXo.js +292 -0
  8. package/dist/BranchesView-DcHZtvXo.js.map +1 -0
  9. package/dist/CronJobsView-CijCToeK.js +456 -0
  10. package/dist/CronJobsView-CijCToeK.js.map +1 -0
  11. package/dist/JSEditor-D8nVp3Lp.js +1308 -0
  12. package/dist/JSEditor-D8nVp3Lp.js.map +1 -0
  13. package/dist/MonacoEditor-CMYEjiRf.js +161 -0
  14. package/dist/MonacoEditor-CMYEjiRf.js.map +1 -0
  15. package/dist/RLSEditor-DBH09u9v.js +1831 -0
  16. package/dist/RLSEditor-DBH09u9v.js.map +1 -0
  17. package/dist/SQLEditor-CkVx9vgr.js +1792 -0
  18. package/dist/SQLEditor-CkVx9vgr.js.map +1 -0
  19. package/dist/SchemaVisualizer-BgD5Zb77.js +1069 -0
  20. package/dist/SchemaVisualizer-BgD5Zb77.js.map +1 -0
  21. package/dist/StorageView-CTqGFhY9.js +907 -0
  22. package/dist/StorageView-CTqGFhY9.js.map +1 -0
  23. package/dist/common/src/collections/CollectionRegistry.d.ts +56 -0
  24. package/dist/common/src/collections/index.d.ts +1 -0
  25. package/dist/common/src/data/buildRebaseData.d.ts +14 -0
  26. package/dist/common/src/index.d.ts +3 -0
  27. package/dist/common/src/util/builders.d.ts +57 -0
  28. package/dist/common/src/util/callbacks.d.ts +6 -0
  29. package/dist/common/src/util/collections.d.ts +11 -0
  30. package/dist/common/src/util/common.d.ts +2 -0
  31. package/dist/common/src/util/conditions.d.ts +26 -0
  32. package/dist/common/src/util/entities.d.ts +58 -0
  33. package/dist/common/src/util/enums.d.ts +3 -0
  34. package/dist/common/src/util/index.d.ts +16 -0
  35. package/dist/common/src/util/navigation_from_path.d.ts +34 -0
  36. package/dist/common/src/util/navigation_utils.d.ts +20 -0
  37. package/dist/common/src/util/parent_references_from_path.d.ts +6 -0
  38. package/dist/common/src/util/paths.d.ts +14 -0
  39. package/dist/common/src/util/permissions.d.ts +5 -0
  40. package/dist/common/src/util/references.d.ts +2 -0
  41. package/dist/common/src/util/relations.d.ts +22 -0
  42. package/dist/common/src/util/resolutions.d.ts +72 -0
  43. package/dist/common/src/util/storage.d.ts +24 -0
  44. package/dist/core/src/components/AIIcon.d.ts +16 -0
  45. package/dist/core/src/components/ConfirmationDialog.d.ts +9 -0
  46. package/dist/core/src/components/Debug/UIReferenceView.d.ts +1 -0
  47. package/dist/core/src/components/Debug/UIStyleGuide.d.ts +1 -0
  48. package/dist/core/src/components/ErrorTooltip.d.ts +2 -0
  49. package/dist/core/src/components/ErrorView.d.ts +21 -0
  50. package/dist/core/src/components/LanguageToggle.d.ts +1 -0
  51. package/dist/core/src/components/LoginView/LoginView.d.ts +68 -0
  52. package/dist/core/src/components/LoginView/index.d.ts +2 -0
  53. package/dist/core/src/components/NotFoundPage.d.ts +1 -0
  54. package/dist/core/src/components/RebaseAuth.d.ts +10 -0
  55. package/dist/core/src/components/RebaseLogo.d.ts +7 -0
  56. package/dist/core/src/components/UnsavedChangesDialog.d.ts +9 -0
  57. package/dist/core/src/components/UserDisplay.d.ts +7 -0
  58. package/dist/core/src/components/UserSelectPopover.d.ts +62 -0
  59. package/dist/core/src/components/UserSettingsView.d.ts +1 -0
  60. package/dist/core/src/components/common/index.d.ts +6 -0
  61. package/dist/core/src/components/common/table_height.d.ts +5 -0
  62. package/dist/core/src/components/common/types.d.ts +63 -0
  63. package/dist/core/src/components/common/useColumnsIds.d.ts +9 -0
  64. package/dist/core/src/components/common/useDataTableController.d.ts +45 -0
  65. package/dist/core/src/components/common/useDebouncedData.d.ts +9 -0
  66. package/dist/core/src/components/common/useScrollRestoration.d.ts +14 -0
  67. package/dist/core/src/components/index.d.ts +16 -0
  68. package/dist/core/src/contexts/AdminModeController.d.ts +4 -0
  69. package/dist/core/src/contexts/AnalyticsContext.d.ts +3 -0
  70. package/dist/core/src/contexts/AuthControllerContext.d.ts +3 -0
  71. package/dist/core/src/contexts/CustomizationControllerContext.d.ts +3 -0
  72. package/dist/core/src/contexts/DataDriverContext.d.ts +3 -0
  73. package/dist/core/src/contexts/DatabaseAdminContext.d.ts +3 -0
  74. package/dist/core/src/contexts/DialogsProvider.d.ts +4 -0
  75. package/dist/core/src/contexts/EffectiveRoleController.d.ts +4 -0
  76. package/dist/core/src/contexts/InternalUserManagementContext.d.ts +3 -0
  77. package/dist/core/src/contexts/ModeController.d.ts +4 -0
  78. package/dist/core/src/contexts/RebaseClientInstanceContext.d.ts +6 -0
  79. package/dist/core/src/contexts/RebaseDataContext.d.ts +3 -0
  80. package/dist/core/src/contexts/SnackbarProvider.d.ts +2 -0
  81. package/dist/core/src/contexts/StorageSourceContext.d.ts +3 -0
  82. package/dist/core/src/contexts/UserConfigurationPersistenceContext.d.ts +3 -0
  83. package/dist/core/src/contexts/index.d.ts +13 -0
  84. package/dist/core/src/core/PluginLifecycleManager.d.ts +17 -0
  85. package/dist/core/src/core/PluginProviderStack.d.ts +21 -0
  86. package/dist/core/src/core/Rebase.d.ts +14 -0
  87. package/dist/core/src/core/RebaseProps.d.ts +136 -0
  88. package/dist/core/src/core/RebaseRouter.d.ts +4 -0
  89. package/dist/core/src/core/RebaseRoutes.d.ts +17 -0
  90. package/dist/core/src/core/index.d.ts +4 -0
  91. package/dist/core/src/hooks/ApiConfigContext.d.ts +24 -0
  92. package/dist/core/src/hooks/data/delete.d.ts +31 -0
  93. package/dist/core/src/hooks/data/save.d.ts +34 -0
  94. package/dist/core/src/hooks/data/useCollectionFetch.d.ts +51 -0
  95. package/dist/core/src/hooks/data/useData.d.ts +13 -0
  96. package/dist/core/src/hooks/data/useDataOrder.d.ts +12 -0
  97. package/dist/core/src/hooks/data/useEntityFetch.d.ts +38 -0
  98. package/dist/core/src/hooks/data/useRelationSelector.d.ts +52 -0
  99. package/dist/core/src/hooks/data/useUserSelector.d.ts +31 -0
  100. package/dist/core/src/hooks/index.d.ts +37 -0
  101. package/dist/core/src/hooks/useAdminModeController.d.ts +19 -0
  102. package/dist/core/src/hooks/useAnalyticsController.d.ts +5 -0
  103. package/dist/core/src/hooks/useAuthController.d.ts +11 -0
  104. package/dist/core/src/hooks/useAuthSubscription.d.ts +2 -0
  105. package/dist/core/src/hooks/useBackendStorageSource.d.ts +30 -0
  106. package/dist/core/src/hooks/useBridgeRegistration.d.ts +18 -0
  107. package/dist/core/src/hooks/useBrowserTitleAndIcon.d.ts +6 -0
  108. package/dist/core/src/hooks/useBuildAdminModeController.d.ts +6 -0
  109. package/dist/core/src/hooks/useBuildEffectiveRoleController.d.ts +8 -0
  110. package/dist/core/src/hooks/useBuildLocalConfigurationPersistence.d.ts +2 -0
  111. package/dist/core/src/hooks/useBuildModeController.d.ts +6 -0
  112. package/dist/core/src/hooks/useClipboard.d.ts +57 -0
  113. package/dist/core/src/hooks/useCollapsedGroups.d.ts +12 -0
  114. package/dist/core/src/hooks/useCustomizationController.d.ts +11 -0
  115. package/dist/core/src/hooks/useDialogsController.d.ts +11 -0
  116. package/dist/core/src/hooks/useEffectiveRoleController.d.ts +7 -0
  117. package/dist/core/src/hooks/useInternalUserManagementController.d.ts +12 -0
  118. package/dist/core/src/hooks/useLargeLayout.d.ts +1 -0
  119. package/dist/core/src/hooks/useModeController.d.ts +19 -0
  120. package/dist/core/src/hooks/usePermissions.d.ts +12 -0
  121. package/dist/core/src/hooks/useRebaseClient.d.ts +5 -0
  122. package/dist/core/src/hooks/useRebaseContext.d.ts +11 -0
  123. package/dist/core/src/hooks/useRebaseRegistry.d.ts +34 -0
  124. package/dist/core/src/hooks/useSlot.d.ts +18 -0
  125. package/dist/core/src/hooks/useSnackbarController.d.ts +20 -0
  126. package/dist/core/src/hooks/useStorageSource.d.ts +7 -0
  127. package/dist/core/src/hooks/useStudioBridge.d.ts +91 -0
  128. package/dist/core/src/hooks/useTranslation.d.ts +17 -0
  129. package/dist/core/src/hooks/useUnsavedChangesDialog.d.ts +12 -0
  130. package/dist/core/src/hooks/useUserConfigurationPersistence.d.ts +8 -0
  131. package/dist/core/src/hooks/useValidateAuthenticator.d.ts +21 -0
  132. package/dist/core/src/i18n/RebaseI18nProvider.d.ts +33 -0
  133. package/dist/core/src/index.d.ts +15 -0
  134. package/dist/core/src/internal/common.d.ts +3 -0
  135. package/dist/core/src/internal/useRestoreScroll.d.ts +6 -0
  136. package/dist/core/src/locales/de.d.ts +2 -0
  137. package/dist/core/src/locales/en.d.ts +10 -0
  138. package/dist/core/src/locales/es.d.ts +10 -0
  139. package/dist/core/src/locales/fr.d.ts +2 -0
  140. package/dist/core/src/locales/hi.d.ts +2 -0
  141. package/dist/core/src/locales/it.d.ts +2 -0
  142. package/dist/core/src/locales/pt.d.ts +7 -0
  143. package/dist/core/src/util/constants.d.ts +1 -0
  144. package/dist/core/src/util/createFormexStub.d.ts +2 -0
  145. package/dist/core/src/util/entity_cache.d.ts +27 -0
  146. package/dist/core/src/util/enums.d.ts +5 -0
  147. package/dist/core/src/util/icon_list.d.ts +5 -0
  148. package/dist/core/src/util/icon_synonyms.d.ts +1 -0
  149. package/dist/core/src/util/icons.d.ts +20 -0
  150. package/dist/core/src/util/index.d.ts +10 -0
  151. package/dist/core/src/util/previews.d.ts +4 -0
  152. package/dist/core/src/util/useStorageUploadController.d.ts +38 -0
  153. package/dist/core/src/util/useTraceUpdate.d.ts +2 -0
  154. package/dist/formex/src/Field.d.ts +52 -0
  155. package/dist/formex/src/Formex.d.ts +7 -0
  156. package/dist/formex/src/index.d.ts +5 -0
  157. package/dist/formex/src/types.d.ts +40 -0
  158. package/dist/formex/src/useCreateFormex.d.ts +14 -0
  159. package/dist/formex/src/utils.d.ts +16 -0
  160. package/dist/index.es.js +726 -0
  161. package/dist/index.es.js.map +1 -0
  162. package/dist/index.umd.js +9647 -0
  163. package/dist/index.umd.js.map +1 -0
  164. package/dist/studio/src/components/ApiExplorer/ApiExplorer.d.ts +9 -0
  165. package/dist/studio/src/components/ApiExplorer/EndpointDetail.d.ts +9 -0
  166. package/dist/studio/src/components/ApiExplorer/TryItPanel.d.ts +15 -0
  167. package/dist/studio/src/components/ApiExplorer/parseSpec.d.ts +16 -0
  168. package/dist/studio/src/components/ApiExplorer/types.d.ts +90 -0
  169. package/dist/studio/src/components/AuthSimulationSelector.d.ts +11 -0
  170. package/dist/studio/src/components/Branches/BranchesView.d.ts +1 -0
  171. package/dist/studio/src/components/CronJobs/CronJobsView.d.ts +1 -0
  172. package/dist/studio/src/components/JSEditor/JSEditor.d.ts +1 -0
  173. package/dist/studio/src/components/JSEditor/JSEditorSidebar.d.ts +21 -0
  174. package/dist/studio/src/components/JSEditor/JSMonacoEditor.d.ts +18 -0
  175. package/dist/studio/src/components/RLSEditor/PolicyEditor.d.ts +9 -0
  176. package/dist/studio/src/components/RLSEditor/RLSEditor.d.ts +19 -0
  177. package/dist/studio/src/components/RLSEditor/index.d.ts +1 -0
  178. package/dist/studio/src/components/RebaseStudio.d.ts +2 -0
  179. package/dist/studio/src/components/SQLEditor/ExplainVisualizer.d.ts +24 -0
  180. package/dist/studio/src/components/SQLEditor/MonacoEditor.d.ts +17 -0
  181. package/dist/studio/src/components/SQLEditor/SQLEditor.d.ts +11 -0
  182. package/dist/studio/src/components/SQLEditor/SQLEditorSidebar.d.ts +21 -0
  183. package/dist/studio/src/components/SQLEditor/SchemaBrowser.d.ts +8 -0
  184. package/dist/studio/src/components/SchemaVisualizer/RelationEdge.d.ts +3 -0
  185. package/dist/studio/src/components/SchemaVisualizer/SchemaVisualizer.d.ts +2 -0
  186. package/dist/studio/src/components/SchemaVisualizer/TableNode.d.ts +3 -0
  187. package/dist/studio/src/components/SchemaVisualizer/index.d.ts +5 -0
  188. package/dist/studio/src/components/SchemaVisualizer/schema-visualizer.utils.d.ts +42 -0
  189. package/dist/studio/src/components/SchemaVisualizer/useSchemaGraph.d.ts +37 -0
  190. package/dist/studio/src/components/StorageView/StorageView.d.ts +1 -0
  191. package/dist/studio/src/components/StudioHomePage.d.ts +9 -0
  192. package/dist/studio/src/index.d.ts +4 -0
  193. package/dist/studio/src/utils/entities.d.ts +0 -0
  194. package/dist/studio/src/utils/pgColumnToProperty.d.ts +6 -0
  195. package/dist/studio/src/utils/sql_utils.d.ts +52 -0
  196. package/dist/types/src/controllers/analytics_controller.d.ts +7 -0
  197. package/dist/types/src/controllers/auth.d.ts +119 -0
  198. package/dist/types/src/controllers/client.d.ts +170 -0
  199. package/dist/types/src/controllers/collection_registry.d.ts +45 -0
  200. package/dist/types/src/controllers/customization_controller.d.ts +60 -0
  201. package/dist/types/src/controllers/data.d.ts +168 -0
  202. package/dist/types/src/controllers/data_driver.d.ts +160 -0
  203. package/dist/types/src/controllers/database_admin.d.ts +11 -0
  204. package/dist/types/src/controllers/dialogs_controller.d.ts +36 -0
  205. package/dist/types/src/controllers/effective_role.d.ts +4 -0
  206. package/dist/types/src/controllers/email.d.ts +34 -0
  207. package/dist/types/src/controllers/index.d.ts +18 -0
  208. package/dist/types/src/controllers/local_config_persistence.d.ts +20 -0
  209. package/dist/types/src/controllers/navigation.d.ts +213 -0
  210. package/dist/types/src/controllers/registry.d.ts +54 -0
  211. package/dist/types/src/controllers/side_dialogs_controller.d.ts +67 -0
  212. package/dist/types/src/controllers/side_entity_controller.d.ts +90 -0
  213. package/dist/types/src/controllers/snackbar.d.ts +24 -0
  214. package/dist/types/src/controllers/storage.d.ts +171 -0
  215. package/dist/types/src/index.d.ts +4 -0
  216. package/dist/types/src/rebase_context.d.ts +105 -0
  217. package/dist/types/src/types/backend.d.ts +536 -0
  218. package/dist/types/src/types/builders.d.ts +15 -0
  219. package/dist/types/src/types/chips.d.ts +5 -0
  220. package/dist/types/src/types/collections.d.ts +856 -0
  221. package/dist/types/src/types/cron.d.ts +102 -0
  222. package/dist/types/src/types/data_source.d.ts +64 -0
  223. package/dist/types/src/types/entities.d.ts +145 -0
  224. package/dist/types/src/types/entity_actions.d.ts +98 -0
  225. package/dist/types/src/types/entity_callbacks.d.ts +173 -0
  226. package/dist/types/src/types/entity_link_builder.d.ts +7 -0
  227. package/dist/types/src/types/entity_overrides.d.ts +10 -0
  228. package/dist/types/src/types/entity_views.d.ts +61 -0
  229. package/dist/types/src/types/export_import.d.ts +21 -0
  230. package/dist/types/src/types/index.d.ts +23 -0
  231. package/dist/types/src/types/locales.d.ts +4 -0
  232. package/dist/types/src/types/modify_collections.d.ts +5 -0
  233. package/dist/types/src/types/plugins.d.ts +279 -0
  234. package/dist/types/src/types/properties.d.ts +1176 -0
  235. package/dist/types/src/types/property_config.d.ts +70 -0
  236. package/dist/types/src/types/relations.d.ts +336 -0
  237. package/dist/types/src/types/slots.d.ts +252 -0
  238. package/dist/types/src/types/translations.d.ts +870 -0
  239. package/dist/types/src/types/user_management_delegate.d.ts +121 -0
  240. package/dist/types/src/types/websockets.d.ts +78 -0
  241. package/dist/types/src/users/index.d.ts +2 -0
  242. package/dist/types/src/users/roles.d.ts +22 -0
  243. package/dist/types/src/users/user.d.ts +46 -0
  244. package/dist/ui/src/components/Alert.d.ts +12 -0
  245. package/dist/ui/src/components/Autocomplete.d.ts +21 -0
  246. package/dist/ui/src/components/Avatar.d.ts +11 -0
  247. package/dist/ui/src/components/Badge.d.ts +8 -0
  248. package/dist/ui/src/components/BooleanSwitch.d.ts +14 -0
  249. package/dist/ui/src/components/BooleanSwitchWithLabel.d.ts +17 -0
  250. package/dist/ui/src/components/Button.d.ts +14 -0
  251. package/dist/ui/src/components/Card.d.ts +9 -0
  252. package/dist/ui/src/components/CenteredView.d.ts +9 -0
  253. package/dist/ui/src/components/Checkbox.d.ts +13 -0
  254. package/dist/ui/src/components/Chip.d.ts +26 -0
  255. package/dist/ui/src/components/CircularProgress.d.ts +5 -0
  256. package/dist/ui/src/components/CircularProgressCenter.d.ts +11 -0
  257. package/dist/ui/src/components/Collapse.d.ts +9 -0
  258. package/dist/ui/src/components/ColorPicker.d.ts +30 -0
  259. package/dist/ui/src/components/Container.d.ts +8 -0
  260. package/dist/ui/src/components/DateTimeField.d.ts +24 -0
  261. package/dist/ui/src/components/DebouncedTextField.d.ts +2 -0
  262. package/dist/ui/src/components/Dialog.d.ts +39 -0
  263. package/dist/ui/src/components/DialogActions.d.ts +7 -0
  264. package/dist/ui/src/components/DialogContent.d.ts +7 -0
  265. package/dist/ui/src/components/DialogTitle.d.ts +10 -0
  266. package/dist/ui/src/components/ErrorBoundary.d.ts +11 -0
  267. package/dist/ui/src/components/ExpandablePanel.d.ts +12 -0
  268. package/dist/ui/src/components/FileUpload.d.ts +23 -0
  269. package/dist/ui/src/components/IconButton.d.ts +12 -0
  270. package/dist/ui/src/components/InfoLabel.d.ts +5 -0
  271. package/dist/ui/src/components/InputLabel.d.ts +11 -0
  272. package/dist/ui/src/components/Label.d.ts +7 -0
  273. package/dist/ui/src/components/LoadingButton.d.ts +7 -0
  274. package/dist/ui/src/components/Markdown.d.ts +10 -0
  275. package/dist/ui/src/components/Menu.d.ts +23 -0
  276. package/dist/ui/src/components/Menubar.d.ts +80 -0
  277. package/dist/ui/src/components/MultiSelect.d.ts +48 -0
  278. package/dist/ui/src/components/Paper.d.ts +6 -0
  279. package/dist/ui/src/components/Popover.d.ts +24 -0
  280. package/dist/ui/src/components/RadioGroup.d.ts +28 -0
  281. package/dist/ui/src/components/ResizablePanels.d.ts +18 -0
  282. package/dist/ui/src/components/SearchBar.d.ts +22 -0
  283. package/dist/ui/src/components/Select.d.ts +43 -0
  284. package/dist/ui/src/components/Separator.d.ts +5 -0
  285. package/dist/ui/src/components/Sheet.d.ts +22 -0
  286. package/dist/ui/src/components/Skeleton.d.ts +6 -0
  287. package/dist/ui/src/components/Slider.d.ts +21 -0
  288. package/dist/ui/src/components/Table.d.ts +34 -0
  289. package/dist/ui/src/components/Tabs.d.ts +19 -0
  290. package/dist/ui/src/components/TextField.d.ts +58 -0
  291. package/dist/ui/src/components/TextareaAutosize.d.ts +43 -0
  292. package/dist/ui/src/components/ToggleButtonGroup.d.ts +30 -0
  293. package/dist/ui/src/components/Tooltip.d.ts +19 -0
  294. package/dist/ui/src/components/Typography.d.ts +36 -0
  295. package/dist/ui/src/components/VirtualTable/VirtualTable.d.ts +11 -0
  296. package/dist/ui/src/components/VirtualTable/VirtualTableCell.d.ts +21 -0
  297. package/dist/ui/src/components/VirtualTable/VirtualTableHeader.d.ts +29 -0
  298. package/dist/ui/src/components/VirtualTable/VirtualTableHeaderRow.d.ts +2 -0
  299. package/dist/ui/src/components/VirtualTable/VirtualTableProps.d.ts +243 -0
  300. package/dist/ui/src/components/VirtualTable/VirtualTableRow.d.ts +3 -0
  301. package/dist/ui/src/components/VirtualTable/index.d.ts +3 -0
  302. package/dist/ui/src/components/VirtualTable/types.d.ts +38 -0
  303. package/dist/ui/src/components/common/SelectInputLabel.d.ts +5 -0
  304. package/dist/ui/src/components/index.d.ts +53 -0
  305. package/dist/ui/src/hooks/PortalContainerContext.d.ts +31 -0
  306. package/dist/ui/src/hooks/index.d.ts +6 -0
  307. package/dist/ui/src/hooks/useDebounceCallback.d.ts +1 -0
  308. package/dist/ui/src/hooks/useDebounceValue.d.ts +1 -0
  309. package/dist/ui/src/hooks/useDebouncedCallback.d.ts +1 -0
  310. package/dist/ui/src/hooks/useInjectStyles.d.ts +7 -0
  311. package/dist/ui/src/hooks/useOutsideAlerter.d.ts +5 -0
  312. package/dist/ui/src/icons/GitHubIcon.d.ts +2 -0
  313. package/dist/ui/src/icons/HandleIcon.d.ts +1 -0
  314. package/dist/ui/src/icons/Icon.d.ts +20 -0
  315. package/dist/ui/src/icons/cool_icon_keys.d.ts +1 -0
  316. package/dist/ui/src/icons/icon_keys.d.ts +1 -0
  317. package/dist/ui/src/icons/index.d.ts +6 -0
  318. package/dist/ui/src/index.d.ts +5 -0
  319. package/dist/ui/src/styles.d.ts +12 -0
  320. package/dist/ui/src/util/chip_colors.d.ts +4 -0
  321. package/dist/ui/src/util/cls.d.ts +2 -0
  322. package/dist/ui/src/util/debounce.d.ts +10 -0
  323. package/dist/ui/src/util/hash.d.ts +1 -0
  324. package/dist/ui/src/util/index.d.ts +4 -0
  325. package/dist/ui/src/util/key_to_icon_component.d.ts +1 -0
  326. package/package.json +84 -0
  327. package/src/components/ApiExplorer/ApiExplorer.tsx +290 -0
  328. package/src/components/ApiExplorer/EndpointDetail.tsx +271 -0
  329. package/src/components/ApiExplorer/TryItPanel.tsx +510 -0
  330. package/src/components/ApiExplorer/parseSpec.ts +104 -0
  331. package/src/components/ApiExplorer/types.ts +84 -0
  332. package/src/components/AuthSimulationSelector.tsx +77 -0
  333. package/src/components/Branches/BranchesView.tsx +370 -0
  334. package/src/components/CronJobs/CronJobsView.tsx +346 -0
  335. package/src/components/JSEditor/JSEditor.tsx +1033 -0
  336. package/src/components/JSEditor/JSEditorSidebar.tsx +340 -0
  337. package/src/components/JSEditor/JSMonacoEditor.tsx +390 -0
  338. package/src/components/RLSEditor/PolicyEditor.tsx +444 -0
  339. package/src/components/RLSEditor/RLSEditor.tsx +692 -0
  340. package/src/components/RLSEditor/index.ts +1 -0
  341. package/src/components/RebaseStudio.tsx +121 -0
  342. package/src/components/SQLEditor/ExplainVisualizer.tsx +128 -0
  343. package/src/components/SQLEditor/MonacoEditor.tsx +203 -0
  344. package/src/components/SQLEditor/SQLEditor.tsx +1419 -0
  345. package/src/components/SQLEditor/SQLEditorSidebar.tsx +174 -0
  346. package/src/components/SQLEditor/SchemaBrowser.tsx +158 -0
  347. package/src/components/SchemaVisualizer/RelationEdge.tsx +102 -0
  348. package/src/components/SchemaVisualizer/SchemaVisualizer.tsx +665 -0
  349. package/src/components/SchemaVisualizer/TableNode.tsx +257 -0
  350. package/src/components/SchemaVisualizer/index.ts +5 -0
  351. package/src/components/SchemaVisualizer/schema-visualizer.utils.ts +140 -0
  352. package/src/components/SchemaVisualizer/useSchemaGraph.ts +397 -0
  353. package/src/components/StorageView/StorageView.tsx +1035 -0
  354. package/src/components/StudioHomePage.tsx +357 -0
  355. package/src/index.ts +31 -0
  356. package/src/utils/entities.ts +2 -0
  357. package/src/utils/pgColumnToProperty.test.ts +401 -0
  358. package/src/utils/pgColumnToProperty.ts +275 -0
  359. package/src/utils/sql_utils.test.ts +265 -0
  360. package/src/utils/sql_utils.ts +291 -0
  361. package/src/vite-env.d.ts +1 -0
@@ -0,0 +1,1033 @@
1
+
2
+ import React, { useState, useEffect, useCallback, useRef, useMemo } from "react";
3
+ import { Highlight, themes } from "prism-react-renderer";
4
+ import { toSnakeCase } from "@rebasepro/utils";
5
+ import { Button, Typography, CircularProgress, cls, IconButton, Dialog, DialogContent, DialogActions, DialogTitle, TextField, Tooltip, defaultBorderMixin, ResizablePanels, Chip, Menu, MenuItem, Tabs, Tab, VirtualTable, VirtualTableColumn, CellRendererParams , iconSize } from "@rebasepro/ui";
6
+ import { TerminalIcon, XIcon, PlusIcon, SaveIcon, DownloadIcon, PlayIcon, MoreVerticalIcon, PencilIcon, MenuIcon } from "lucide-react";
7
+ import { useStudioUrlController, useStudioCollectionRegistry, useStudioSideEntityController } from "@rebasepro/core";
8
+ import { useRebaseContext, useRebaseClient, useSnackbarController, useApiConfig, useTranslation, useModeController, ErrorView, SelectableUser, IconForView } from "@rebasepro/core";
9
+ import { EntityCollection } from "@rebasepro/types";
10
+ import { createRebaseClient } from "@rebasepro/client";
11
+ import { JSMonacoEditor } from "./JSMonacoEditor";
12
+ import { JSEditorSidebar, JSSnippet } from "./JSEditorSidebar";
13
+ import { AuthSimulationSelector } from "../AuthSimulationSelector";
14
+
15
+ // ─── Types ───────────────────────────────────────────────────────────
16
+
17
+ interface ConsoleEntry {
18
+ type: "log" | "warn" | "error" | "info";
19
+ args: any[];
20
+ timestamp: number;
21
+ }
22
+
23
+ interface ExecutionResult {
24
+ value: any;
25
+ console: ConsoleEntry[];
26
+ duration: number;
27
+ error?: string;
28
+ timestamp: number;
29
+ }
30
+
31
+ interface EditorTab {
32
+ id: string;
33
+ name: string;
34
+ code: string;
35
+ }
36
+
37
+ // SelectedUser is now SelectableUser from @rebasepro/core
38
+
39
+ // ─── Constants ───────────────────────────────────────────────────────
40
+
41
+ const STORAGE_PREFIX = "rebase_js_";
42
+ const MAX_HISTORY = 50;
43
+
44
+ const DEFAULT_CODE = `// Available: client (RebaseClient)
45
+ // Press Cmd+Enter (Ctrl+Enter) to run
46
+ //
47
+ // Examples:
48
+ // const result = await client.data.collection("your_collection").find({ limit: 10 });
49
+ // const users = await client.admin.listUsers();
50
+ // const session = client.auth.getSession();
51
+
52
+ const result = await client.data.collection("your_collection").find({ limit: 10 });
53
+ return result;
54
+ `;
55
+
56
+ // ─── Helpers ─────────────────────────────────────────────────────────
57
+
58
+ function loadFromStorage<T>(key: string, fallback: T): T {
59
+ try {
60
+ const raw = localStorage.getItem(STORAGE_PREFIX + key);
61
+ return raw ? JSON.parse(raw) : fallback;
62
+ } catch {
63
+ return fallback;
64
+ }
65
+ }
66
+
67
+ function saveToStorage<T>(key: string, value: T) {
68
+ try {
69
+ localStorage.setItem(STORAGE_PREFIX + key, JSON.stringify(value));
70
+ } catch { /* quota */ }
71
+ }
72
+
73
+ function formatJSON(value: any): string {
74
+ try {
75
+ return JSON.stringify(value, null, 2);
76
+ } catch {
77
+ return String(value);
78
+ }
79
+ }
80
+
81
+ // ─── Helpers: Collection matching for JS results ────────────────────
82
+
83
+ interface MatchedJSCollection {
84
+ collectionSlug: string;
85
+ collection: EntityCollection;
86
+ pkColumn: string;
87
+ }
88
+
89
+ /**
90
+ * Given the raw SDK result, try to detect which collections are present.
91
+ * JS SDK results typically come back as `{ data: [{ id, values }] }` or plain arrays.
92
+ * The heuristic: if the executed code contains `collection("<slug>")` or `client.data.<slug>`,
93
+ * and the result has rows with an "id" column, we match those slugs.
94
+ */
95
+ function detectCollectionsInResult(
96
+ code: string,
97
+ resultValue: any,
98
+ collections: EntityCollection[]
99
+ ): MatchedJSCollection[] {
100
+ if (!resultValue || !collections?.length) return [];
101
+
102
+ // Extract collection slugs mentioned in the code
103
+ const mentionedSlugs = new Set<string>();
104
+
105
+ // Match: collection("slug") or collection('slug')
106
+ const collectionCallRegex = /\.collection\(["']([^"']+)["']\)/g;
107
+ let m: RegExpExecArray | null;
108
+ while ((m = collectionCallRegex.exec(code)) !== null) {
109
+ mentionedSlugs.add(m[1]);
110
+ }
111
+
112
+ // Match: client.data.<slug>. (dot-access pattern)
113
+ const dotAccessRegex = /client\.data\.([a-zA-Z_][a-zA-Z0-9_]*)\./g;
114
+ while ((m = dotAccessRegex.exec(code)) !== null) {
115
+ if (!["collection", "find", "findById", "create", "update", "delete"].includes(m[1])) {
116
+ mentionedSlugs.add(m[1]);
117
+ }
118
+ }
119
+
120
+ if (mentionedSlugs.size === 0) return [];
121
+
122
+ // Check if result has rows with an "id" field
123
+ let rows: any[] = [];
124
+ if (resultValue?.data && Array.isArray(resultValue.data)) {
125
+ rows = resultValue.data;
126
+ } else if (Array.isArray(resultValue)) {
127
+ rows = resultValue;
128
+ }
129
+
130
+ const hasId = rows.length > 0 && rows.some(r => r?.id != null);
131
+ if (!hasId) return [];
132
+
133
+ const matched: MatchedJSCollection[] = [];
134
+ for (const slug of mentionedSlugs) {
135
+ const normalised = toSnakeCase(slug);
136
+ const col = collections.find(c => {
137
+ const tableName = (c as any).table || toSnakeCase(c.slug);
138
+ return c.slug === slug || tableName === normalised || toSnakeCase(c.slug) === normalised;
139
+ });
140
+ if (col) {
141
+ matched.push({
142
+ collectionSlug: col.slug,
143
+ collection: col,
144
+ pkColumn: "id"
145
+ });
146
+ }
147
+ }
148
+
149
+ return matched;
150
+ }
151
+
152
+ // ─── Main Component ─────────────────────────────────────────────────
153
+
154
+ export function JSEditor() {
155
+ // Contexts
156
+ const rebaseContext = useRebaseContext();
157
+ const rebaseClient = useRebaseClient();
158
+ const apiConfig = useApiConfig();
159
+ const snackbar = useSnackbarController();
160
+ const collectionRegistry = useStudioCollectionRegistry();
161
+ const sideEntityController = useStudioSideEntityController();
162
+ const { t } = useTranslation();
163
+
164
+ // User management for the "Run as" picker
165
+ const userManagement = rebaseContext.userManagement;
166
+ const currentUser = rebaseContext.authController?.user;
167
+
168
+ // State
169
+ const [tabs, setTabs] = useState<EditorTab[]>(() =>
170
+ loadFromStorage("tabs", [{ id: "1",
171
+ name: "Script 1",
172
+ code: DEFAULT_CODE }])
173
+ );
174
+ const [activeTabId, setActiveTabId] = useState<string>(() =>
175
+ loadFromStorage("activeTab", "1")
176
+ );
177
+ const [result, setResult] = useState<ExecutionResult | null>(null);
178
+ const [isRunning, setIsRunning] = useState(false);
179
+ const [snippets, setSnippets] = useState<JSSnippet[]>(() =>
180
+ loadFromStorage("snippets", [])
181
+ );
182
+ const [history, setHistory] = useState<string[]>(() =>
183
+ loadFromStorage("history", [])
184
+ );
185
+ const [showSaveDialog, setShowSaveDialog] = useState(false);
186
+ const [snippetName, setSnippetName] = useState("");
187
+ const [resultView, setResultView] = useState<"json" | "table" | "console">("json");
188
+
189
+ // "Run as" user state — null means "self" (current admin)
190
+ const [selectedUser, setSelectedUser] = useState<SelectableUser | null>(null);
191
+ const [authMode, setAuthMode] = useState<"jwt" | "none">("jwt");
192
+
193
+ const [sidebarSize, setSidebarSize] = useState<number>(() => {
194
+ try {
195
+ const stored = localStorage.getItem(STORAGE_PREFIX + "sidebar_size");
196
+ return stored ? Number(stored) : 18;
197
+ } catch { return 18; }
198
+ });
199
+ const [editorHeight, setEditorHeight] = useState<number>(() => {
200
+ try {
201
+ const stored = localStorage.getItem(STORAGE_PREFIX + "editor_height");
202
+ return stored ? Number(stored) : 55;
203
+ } catch { return 55; }
204
+ });
205
+
206
+ // Derived
207
+ const activeTab = tabs.find(t => t.id === activeTabId) ?? tabs[0];
208
+
209
+ // Collection info for the sidebar and Monaco
210
+ const collectionInfos = useMemo(() => {
211
+ const collections = collectionRegistry?.collections ?? [];
212
+ return collections.map(col => ({
213
+ slug: col.slug,
214
+ name: col.name,
215
+ properties: Object.keys(col.properties ?? {})
216
+ }));
217
+ }, [collectionRegistry?.collections]);
218
+
219
+ const collectionSlugs = useMemo(() => collectionInfos.map(c => c.slug), [collectionInfos]);
220
+
221
+ // Users list for the picker — mapped to SelectableUser shape
222
+ const users = useMemo((): SelectableUser[] => {
223
+ const managed = (userManagement?.users ?? []).map(u => ({
224
+ uid: u.uid,
225
+ displayName: u.displayName,
226
+ email: u.email,
227
+ photoURL: u.photoURL,
228
+ roles: u.roles
229
+ }));
230
+ // Ensure the current user is in the list
231
+ if (currentUser && !managed.some(u => u.uid === currentUser.uid)) {
232
+ managed.unshift({
233
+ uid: currentUser.uid,
234
+ displayName: currentUser.displayName,
235
+ email: currentUser.email,
236
+ photoURL: currentUser.photoURL,
237
+ roles: currentUser.roles
238
+ });
239
+ }
240
+ return managed;
241
+ }, [userManagement?.users, currentUser]);
242
+
243
+ // Current user as SelectableUser for the popover
244
+ const currentSelectableUser = useMemo((): SelectableUser | null => {
245
+ if (!currentUser) return null;
246
+ return {
247
+ uid: currentUser.uid,
248
+ displayName: currentUser.displayName,
249
+ email: currentUser.email,
250
+ photoURL: currentUser.photoURL,
251
+ roles: currentUser.roles
252
+ };
253
+ }, [currentUser]);
254
+
255
+ // ─── Persistence ─────────────────────────────────────────────
256
+
257
+ useEffect(() => { saveToStorage("tabs", tabs); }, [tabs]);
258
+ useEffect(() => { saveToStorage("activeTab", activeTabId); }, [activeTabId]);
259
+ useEffect(() => { saveToStorage("snippets", snippets); }, [snippets]);
260
+ useEffect(() => { saveToStorage("history", history); }, [history]);
261
+
262
+ useEffect(() => {
263
+ try { localStorage.setItem(STORAGE_PREFIX + "sidebar_size", sidebarSize.toString()); } catch { /* ignore */ }
264
+ }, [sidebarSize]);
265
+
266
+ useEffect(() => {
267
+ try { localStorage.setItem(STORAGE_PREFIX + "editor_height", editorHeight.toString()); } catch { /* ignore */ }
268
+ }, [editorHeight]);
269
+
270
+ // ─── Tab management ──────────────────────────────────────────
271
+
272
+ const updateActiveCode = useCallback((code: string | undefined) => {
273
+ setTabs(prev => prev.map(t => t.id === activeTabId ? { ...t,
274
+ code: code ?? "" } : t));
275
+ }, [activeTabId]);
276
+
277
+ const addTab = useCallback(() => {
278
+ const id = String(Date.now());
279
+ const newTab: EditorTab = { id,
280
+ name: `Script ${tabs.length + 1}`,
281
+ code: DEFAULT_CODE };
282
+ setTabs(prev => [...prev, newTab]);
283
+ setActiveTabId(id);
284
+ }, [tabs.length]);
285
+
286
+ const closeTab = useCallback((tabId: string) => {
287
+ setTabs(prev => {
288
+ const filtered = prev.filter(t => t.id !== tabId);
289
+ if (filtered.length === 0) {
290
+ const fresh = { id: String(Date.now()),
291
+ name: "Script 1",
292
+ code: DEFAULT_CODE };
293
+ setActiveTabId(fresh.id);
294
+ return [fresh];
295
+ }
296
+ if (activeTabId === tabId) {
297
+ setActiveTabId(filtered[filtered.length - 1].id);
298
+ }
299
+ return filtered;
300
+ });
301
+ }, [activeTabId]);
302
+
303
+ // ─── Create an authenticated client for execution ────────────
304
+
305
+ const buildClient = useCallback(async () => {
306
+ const apiUrl = apiConfig?.apiUrl;
307
+ const getAuthToken = apiConfig?.getAuthToken;
308
+
309
+ if (!apiUrl) {
310
+ throw new Error("API URL not configured. Make sure apiUrl is set.");
311
+ }
312
+
313
+ if (authMode === "none") {
314
+ const client = createRebaseClient({
315
+ baseUrl: apiUrl,
316
+ token: undefined
317
+ });
318
+ return { client, isScoped: true };
319
+ }
320
+
321
+ // If not running as another user safely reuse the application's global client.
322
+ if (!selectedUser || (currentUser && selectedUser.uid === currentUser.uid)) {
323
+ if (!rebaseClient) {
324
+ throw new Error("Application client is not initialized.");
325
+ }
326
+ return { client: rebaseClient, isScoped: false };
327
+ }
328
+
329
+ // Get the current auth token
330
+ let token: string | undefined;
331
+ if (getAuthToken) {
332
+ token = (await getAuthToken()) ?? undefined;
333
+ }
334
+
335
+ if (!token) {
336
+ throw new Error("No auth token available. Please sign in first.");
337
+ }
338
+
339
+ // Create a fresh 'scoped' client. This enables us to attach custom auth later for this specific execution
340
+ // without mutating the global client.
341
+ const client = createRebaseClient({
342
+ baseUrl: apiUrl,
343
+ token
344
+ });
345
+
346
+ return { client,
347
+ isScoped: true };
348
+ }, [apiConfig, rebaseClient, selectedUser, currentUser, authMode]);
349
+
350
+ // ─── Execution engine ────────────────────────────────────────
351
+
352
+ const executeCode = useCallback(async (codeOverride?: string) => {
353
+ const code = codeOverride ?? activeTab?.code ?? "";
354
+ if (!code.trim()) return;
355
+
356
+ setIsRunning(true);
357
+ setResult(null);
358
+
359
+ // Add to history
360
+ setHistory(prev => {
361
+ const deduped = prev.filter(h => h !== code);
362
+ return [...deduped, code].slice(-MAX_HISTORY);
363
+ });
364
+
365
+ const consoleEntries: ConsoleEntry[] = [];
366
+ const startTime = performance.now();
367
+ let scopedClientToCleanUp: any = null;
368
+
369
+ // Capture console methods
370
+ const originalConsole = {
371
+ log: console.log,
372
+ warn: console.warn,
373
+ error: console.error,
374
+ info: console.info
375
+ };
376
+
377
+ const captureConsole = (type: ConsoleEntry["type"]) => (...args: any[]) => {
378
+ consoleEntries.push({ type,
379
+ args,
380
+ timestamp: Date.now() });
381
+ originalConsole[type](...args);
382
+ };
383
+
384
+ try {
385
+ console.log = captureConsole("log");
386
+ console.warn = captureConsole("warn");
387
+ console.error = captureConsole("error");
388
+ console.info = captureConsole("info");
389
+
390
+ // Build an authenticated client
391
+ const { client, isScoped } = await buildClient();
392
+ if (isScoped) {
393
+ scopedClientToCleanUp = client;
394
+ }
395
+
396
+ // Build context object with useful info about selected user
397
+ const context = {
398
+ user: selectedUser ?? (currentUser ? {
399
+ uid: currentUser.uid,
400
+ displayName: currentUser.displayName,
401
+ email: currentUser.email,
402
+ roles: currentUser.roles
403
+ } : null),
404
+ collections: collectionInfos
405
+ };
406
+
407
+ // Create async function with `client` and `context` injected
408
+
409
+ const AsyncFunction = Object.getPrototypeOf(async function () { }).constructor;
410
+ const fn = new AsyncFunction("client", "context", code);
411
+
412
+ const value = await fn(client, context);
413
+ const duration = performance.now() - startTime;
414
+
415
+ setResult({
416
+ value,
417
+ console: consoleEntries,
418
+ duration,
419
+ timestamp: Date.now()
420
+ });
421
+
422
+ // Auto-detect best view
423
+ if (value?.data && Array.isArray(value.data)) {
424
+ setResultView("table");
425
+ } else if (consoleEntries.length > 0 && value === undefined) {
426
+ setResultView("console");
427
+ } else {
428
+ setResultView("json");
429
+ }
430
+ } catch (err: any) {
431
+ const duration = performance.now() - startTime;
432
+ setResult({
433
+ value: undefined,
434
+ console: consoleEntries,
435
+ duration,
436
+ error: err?.message || String(err),
437
+ timestamp: Date.now()
438
+ });
439
+ setResultView("json");
440
+ } finally {
441
+ console.log = originalConsole.log;
442
+ console.warn = originalConsole.warn;
443
+ console.error = originalConsole.error;
444
+ console.info = originalConsole.info;
445
+ setIsRunning(false);
446
+
447
+ if (scopedClientToCleanUp) {
448
+ // Ensure we disconnect WebSockets to prevent leaking connections
449
+ scopedClientToCleanUp.ws?.disconnect();
450
+ }
451
+ }
452
+ }, [activeTab?.code, buildClient, selectedUser, currentUser, collectionInfos]);
453
+
454
+ // ─── Snippet management ──────────────────────────────────────
455
+
456
+ const saveSnippet = useCallback(() => {
457
+ if (!snippetName.trim() || !activeTab?.code.trim()) return;
458
+ const snippet: JSSnippet = {
459
+ id: String(Date.now()),
460
+ name: snippetName.trim(),
461
+ code: activeTab.code,
462
+ createdAt: Date.now()
463
+ };
464
+ setSnippets(prev => [snippet, ...prev]);
465
+ setShowSaveDialog(false);
466
+ setSnippetName("");
467
+ snackbar.open({ type: "success",
468
+ message: "Snippet saved" });
469
+ }, [snippetName, activeTab?.code, snackbar]);
470
+
471
+ const deleteSnippet = useCallback((id: string) => {
472
+ setSnippets(prev => prev.filter(s => s.id !== id));
473
+ }, []);
474
+
475
+ // ─── Export ──────────────────────────────────────────────────
476
+
477
+ const exportResult = useCallback(() => {
478
+ if (!result?.value) return;
479
+ const blob = new Blob([formatJSON(result.value)], { type: "application/json" });
480
+ const url = URL.createObjectURL(blob);
481
+ const a = document.createElement("a");
482
+ a.href = url;
483
+ a.download = `rebase-result-${Date.now()}.json`;
484
+ a.click();
485
+ URL.revokeObjectURL(url);
486
+ }, [result]);
487
+
488
+ // ─── Table columns for array data ────────────────────────────
489
+
490
+ const tableData = useMemo(() => {
491
+ if (!result?.value) return { columns: [] as VirtualTableColumn[],
492
+ data: [] as Record<string, unknown>[] };
493
+
494
+ let rows: any[] = [];
495
+ if (result.value?.data && Array.isArray(result.value.data)) {
496
+ rows = result.value.data.map((entity: any) => ({
497
+ id: entity.id,
498
+ ...entity.values,
499
+ ...(entity.values ? {} : entity)
500
+ }));
501
+ } else if (Array.isArray(result.value)) {
502
+ rows = result.value;
503
+ }
504
+
505
+ if (rows.length === 0) return { columns: [] as VirtualTableColumn[],
506
+ data: [] as Record<string, unknown>[] };
507
+
508
+ const keys = new Set<string>();
509
+ rows.slice(0, 20).forEach(row => {
510
+ if (row && typeof row === "object") {
511
+ Object.keys(row).forEach(k => keys.add(k));
512
+ }
513
+ });
514
+
515
+ const columns: VirtualTableColumn[] = Array.from(keys).map(key => ({
516
+ key,
517
+ title: key,
518
+ width: key === "id" ? 100 : 200
519
+ }));
520
+
521
+ return { columns,
522
+ data: rows };
523
+ }, [result]);
524
+
525
+ // ─── Matched collections for entity actions ──────────────────
526
+
527
+ const matchedCollections = useMemo(() => {
528
+ if (!result?.value || result.error) return [];
529
+ return detectCollectionsInResult(
530
+ activeTab?.code ?? "",
531
+ result.value,
532
+ collectionRegistry?.collections ?? []
533
+ );
534
+ }, [result, activeTab?.code, collectionRegistry?.collections]);
535
+
536
+ const getRowEntityActions = useCallback((rowData: any): { collection: MatchedJSCollection; entityId: string | number }[] => {
537
+ if (!rowData || matchedCollections.length === 0) return [];
538
+ return matchedCollections
539
+ .filter(mc => rowData[mc.pkColumn] != null)
540
+ .map(mc => ({
541
+ collection: mc,
542
+ entityId: rowData[mc.pkColumn]
543
+ }));
544
+ }, [matchedCollections]);
545
+
546
+ // ─── Export handlers ─────────────────────────────────────────
547
+
548
+ const handleExportCSV = useCallback(() => {
549
+ if (tableData.data.length === 0) return;
550
+ const headers = tableData.columns.map(c => c.key).join(",");
551
+ const rows = tableData.data.map(row =>
552
+ tableData.columns.map(c => {
553
+ const val = row[c.key];
554
+ const str = val === null || val === undefined ? "" : String(val);
555
+ return str.includes(",") ? `"${str}"` : str;
556
+ }).join(",")
557
+ );
558
+ const csv = [headers, ...rows].join("\n");
559
+ const blob = new Blob([csv], { type: "text/csv" });
560
+ const url = URL.createObjectURL(blob);
561
+ const a = document.createElement("a");
562
+ a.href = url;
563
+ a.download = `js_results_${new Date().toISOString().slice(0, 19)}.csv`;
564
+ a.click();
565
+ URL.revokeObjectURL(url);
566
+ }, [tableData]);
567
+
568
+ const handleExportMarkdown = useCallback(() => {
569
+ if (tableData.data.length === 0) return;
570
+ const headers = tableData.columns.map(c => c.key);
571
+ const headerRow = `| ${headers.join(" | ")} |`;
572
+ const dividerRow = `| ${headers.map(() => "---").join(" | ")} |`;
573
+ const dataRows = tableData.data.map(row =>
574
+ `| ${headers.map(h => {
575
+ const val = row[h];
576
+ if (val === null || val === undefined) return "";
577
+ return String(val).replace(/\|/g, "\\|").replace(/\n/g, " ");
578
+ }).join(" | ")} |`
579
+ );
580
+ const markdown = [headerRow, dividerRow, ...dataRows].join("\n");
581
+ navigator.clipboard.writeText(markdown).then(() => {
582
+ snackbar.open({ type: "success",
583
+ message: t("studio_sql_markdown_copied") });
584
+ }).catch(() => {
585
+ snackbar.open({ type: "error",
586
+ message: t("studio_sql_markdown_copy_failed") });
587
+ });
588
+ }, [tableData, snackbar, t]);
589
+
590
+ // ─── Render ──────────────────────────────────────────────────
591
+
592
+ return (
593
+ <div className="flex flex-col h-full w-full">
594
+ {/* Main content */}
595
+ <div className="flex-grow overflow-hidden">
596
+ <ResizablePanels
597
+ orientation="horizontal"
598
+ panelSizePercent={sidebarSize}
599
+ onPanelSizeChange={setSidebarSize}
600
+ minPanelSizePx={200}
601
+ firstPanel={
602
+ <JSEditorSidebar
603
+ collections={collectionInfos}
604
+ snippets={snippets}
605
+ history={history}
606
+ onSelectSnippet={(code) => updateActiveCode(code)}
607
+ onDeleteSnippet={deleteSnippet}
608
+ onInsertCode={(code) => updateActiveCode(code)}
609
+ />
610
+ }
611
+ secondPanel={
612
+ <div className="flex flex-col h-full overflow-hidden">
613
+ {/* Toolbar: matching SQL Editor layout */}
614
+ <div className={cls("flex items-center justify-between pr-2 border-b bg-white dark:bg-surface-950", defaultBorderMixin)}>
615
+ <div className="flex items-center flex-grow overflow-hidden mr-4">
616
+ <div className="flex items-center no-scrollbar overflow-x-auto min-w-0">
617
+ <Tabs value={activeTabId} onValueChange={setActiveTabId} variant="boxy" className="w-[unset] flex-shrink-0" innerClassName="bg-white dark:bg-surface-950">
618
+ {tabs.map(tab => (
619
+ <Tab key={tab.id} value={tab.id} className="flex items-center justify-between group max-w-[200px]">
620
+ <TerminalIcon size={iconSize.smallest} className="text-amber-500 mr-1.5 flex-shrink-0"/>
621
+ <span className="truncate">{tab.name}</span>
622
+ {tabs.length > 1 && (
623
+ <IconButton
624
+ size="smallest"
625
+ onClick={(e) => { e.stopPropagation(); closeTab(tab.id); }}
626
+ className="ml-1 !p-0.5 opacity-0 group-hover:opacity-100 hover:text-red-500 transition-opacity"
627
+ >
628
+ <XIcon size={iconSize.smallest}/>
629
+ </IconButton>
630
+ )}
631
+ </Tab>
632
+ ))}
633
+ </Tabs>
634
+ <IconButton
635
+ size="small"
636
+ onClick={addTab}
637
+ className="ml-2 flex-shrink-0"
638
+ >
639
+ <PlusIcon size={iconSize.smallest}/>
640
+ </IconButton>
641
+ </div>
642
+ </div>
643
+ <div className="flex shrink-0 items-center justify-end gap-1.5">
644
+ <Tooltip title="SaveIcon as snippet">
645
+ <IconButton
646
+ size="small"
647
+ onClick={() => {
648
+ setSnippetName("");
649
+ setShowSaveDialog(true);
650
+ }}
651
+ disabled={!activeTab?.code.trim()}
652
+ >
653
+ <SaveIcon size={iconSize.smallest}/>
654
+ </IconButton>
655
+ </Tooltip>
656
+
657
+ {result?.value && (
658
+ <Tooltip title="Export result as JSON">
659
+ <IconButton size="small" onClick={exportResult}>
660
+ <DownloadIcon size={iconSize.smallest}/>
661
+ </IconButton>
662
+ </Tooltip>
663
+ )}
664
+
665
+ <Button
666
+ size="small"
667
+ color="primary"
668
+ disabled={isRunning || !activeTab?.code.trim()}
669
+ onClick={() => executeCode()}
670
+ >
671
+ {isRunning ? <CircularProgress size="smallest" className="mr-2"/> : <PlayIcon size={iconSize.smallest} className="mr-2"/>}
672
+ Run
673
+ </Button>
674
+ </div>
675
+ </div>
676
+
677
+ {/* Editor + Results split */}
678
+ <div className="flex-grow overflow-hidden">
679
+ <ResizablePanels
680
+ orientation="vertical"
681
+ panelSizePercent={editorHeight}
682
+ onPanelSizeChange={setEditorHeight}
683
+ minPanelSizePx={100}
684
+ firstPanel={
685
+ <div className="h-full w-full overflow-hidden flex flex-col">
686
+ {/* Auth Simulation UI */}
687
+ <div className="p-2 px-3 border-b border-surface-200 dark:border-surface-950 bg-surface-50 dark:bg-surface-900 flex items-center shrink-0">
688
+ <AuthSimulationSelector
689
+ authMode={authMode}
690
+ setAuthMode={setAuthMode}
691
+ selectedUser={selectedUser}
692
+ setSelectedUser={setSelectedUser}
693
+ users={users}
694
+ loading={userManagement?.loading}
695
+ currentUser={currentSelectableUser}
696
+ />
697
+ </div>
698
+ <div className="flex-1 min-h-0">
699
+ <JSMonacoEditor
700
+ value={activeTab?.code ?? ""}
701
+ onChange={updateActiveCode}
702
+ onRun={(selectedText) => executeCode(selectedText)}
703
+ collectionSlugs={collectionSlugs}
704
+ collections={collectionInfos}
705
+ autoFocus
706
+ />
707
+ </div>
708
+ </div>
709
+ }
710
+ secondPanel={
711
+ <div className="h-full w-full flex flex-col bg-surface-50 dark:bg-surface-950 overflow-hidden min-h-0">
712
+ {/* Result header — matches SQL editor */}
713
+ <div className={cls("p-2 px-4 bg-surface-100 dark:bg-surface-900 border-b shrink-0 flex items-center", defaultBorderMixin)}>
714
+ <Typography variant="caption" className="font-bold text-text-disabled dark:text-text-disabled-dark uppercase tracking-widest text-[10px]">
715
+ {t("studio_sql_query_results")}
716
+ </Typography>
717
+
718
+ {result && (
719
+ <>
720
+ <div className="flex-grow"/>
721
+
722
+ <Tabs value={resultView} onValueChange={(val) => setResultView(val as "json" | "table" | "console")} variant="pill" className="w-[unset] mr-2">
723
+ <Tab value="json">JSON</Tab>
724
+ {tableData.data.length > 0 && <Tab value="table">Table</Tab>}
725
+ {result.console.length > 0 && <Tab value="console">Console ({result.console.length})</Tab>}
726
+ </Tabs>
727
+
728
+ <Chip size="smallest" colorScheme={result.error ? "redDarker" : "greenDarker"}>
729
+ {result.error ? "Error" : `${result.duration.toFixed(0)}ms`}
730
+ </Chip>
731
+ </>
732
+ )}
733
+ </div>
734
+
735
+ {/* Result content */}
736
+ <div className="flex-grow flex flex-col min-h-0 overflow-hidden">
737
+ {isRunning && (
738
+ <div className="flex-grow flex items-center justify-center">
739
+ <div className="text-center">
740
+ <CircularProgress size="medium"/>
741
+ <Typography variant="body2" className="mt-4 text-text-secondary dark:text-text-secondary-dark font-mono tracking-tight animate-pulse">
742
+ EXECUTING SCRIPT...
743
+ </Typography>
744
+ </div>
745
+ </div>
746
+ )}
747
+
748
+ {!isRunning && !result && (
749
+ <div className="flex-grow flex items-center justify-center text-text-disabled dark:text-text-disabled-dark">
750
+ <div className="text-center">
751
+ <svg className="w-12 h-12 mx-auto mb-4 opacity-50" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1} d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/></svg>
752
+ <Typography variant="body2">Write JavaScript and press <kbd className="px-1.5 py-0.5 rounded bg-surface-200 dark:bg-surface-700 text-[11px] font-mono">⌘ Enter</kbd> to run</Typography>
753
+ </div>
754
+ </div>
755
+ )}
756
+
757
+ {!isRunning && result?.error && (
758
+ <div className="flex-grow flex items-center justify-center p-6 overflow-auto">
759
+ <ErrorView
760
+ title="Execution Error"
761
+ error={result.error}
762
+ />
763
+ </div>
764
+ )}
765
+
766
+ {!isRunning && result && !result.error && resultView === "json" && (
767
+ <pre className="flex-grow overflow-auto p-4 text-[13px] font-mono leading-relaxed text-text-primary dark:text-text-primary-dark whitespace-pre-wrap break-words">
768
+ {result.value === undefined ? (
769
+ <span className="text-text-disabled italic">undefined (no return value)</span>
770
+ ) : (
771
+ <JSONHighlight value={result.value}/>
772
+ )}
773
+ </pre>
774
+ )}
775
+
776
+ {!isRunning && result && !result.error && resultView === "table" && tableData.data.length > 0 && (
777
+ <div className="flex-grow flex flex-col overflow-hidden min-h-0">
778
+ {/* Collection badges bar — matching SQL editor */}
779
+ {matchedCollections.length > 0 && (
780
+ <div className={cls("px-4 py-1.5 border-b flex items-center gap-2 shrink-0 bg-surface-50 dark:bg-surface-900", defaultBorderMixin)}>
781
+ <Tooltip title={t("studio_sql_cms_collections_tooltip")}>
782
+ <Typography variant="caption" className="text-[10px] font-bold uppercase tracking-widest text-text-disabled dark:text-text-disabled-dark mr-1 shrink-0 cursor-help">{t("studio_sql_cms")}</Typography>
783
+ </Tooltip>
784
+ <div className="flex items-center gap-1.5 overflow-x-auto no-scrollbar">
785
+ {matchedCollections.map(mc => (
786
+ <Tooltip key={mc.collectionSlug} title={`${mc.collection.name} (${mc.collectionSlug})`}>
787
+ <span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[11px] font-medium bg-primary/10 dark:bg-primary-dark/15 text-primary dark:text-primary-dark whitespace-nowrap border border-primary/20 dark:border-primary-dark/20">
788
+ {typeof mc.collection.icon === "string" && (
789
+ <IconForView collectionOrView={{ icon: mc.collection.icon } as never} className="text-[12px]"/>
790
+ )}
791
+ {mc.collection.name}
792
+ </span>
793
+ </Tooltip>
794
+ ))}
795
+ </div>
796
+ </div>
797
+ )}
798
+ <div className="flex-grow relative h-full min-h-0 min-w-0">
799
+ <VirtualTable
800
+ data={tableData.data}
801
+ columns={
802
+ matchedCollections.length > 0
803
+ ? [{ key: "__entity_action__",
804
+ title: "",
805
+ width: 36,
806
+ sortable: false,
807
+ resizable: false }, ...tableData.columns]
808
+ : tableData.columns
809
+ }
810
+ rowHeight={32}
811
+ headerHeight={32}
812
+ cellRenderer={({ rowData, column, rowIndex }: CellRendererParams<Record<string, unknown>>) => {
813
+ // Entity action column
814
+ if (column.key === "__entity_action__") {
815
+ const rowActions = getRowEntityActions(rowData);
816
+ if (rowActions.length === 0) return <div className="h-full w-full"/>;
817
+ if (rowActions.length === 1) {
818
+ const ra = rowActions[0];
819
+ return (
820
+ <div className="h-full flex items-center justify-center">
821
+ <Tooltip title={t("studio_sql_edit_entity", { name: ra.collection.collection.name,
822
+ id: String(ra.entityId) })}>
823
+ <IconButton
824
+ size="small"
825
+ className="text-surface-400 dark:text-surface-500 hover:text-surface-600 dark:hover:text-surface-300"
826
+ onClick={(e) => {
827
+ e.stopPropagation();
828
+ sideEntityController.open({
829
+ path: ra.collection.collectionSlug,
830
+ entityId: ra.entityId,
831
+ collection: ra.collection.collection,
832
+ updateUrl: false
833
+ });
834
+ }}
835
+ >
836
+ <PencilIcon size={iconSize.smallest}/>
837
+ </IconButton>
838
+ </Tooltip>
839
+ </div>
840
+ );
841
+ }
842
+ // Multiple matched collections
843
+ return (
844
+ <div className="h-full flex items-center justify-center">
845
+ <Menu
846
+ trigger={
847
+ <IconButton
848
+ size={"small"}
849
+ className="text-surface-400 dark:text-surface-500 hover:text-surface-600 dark:hover:text-surface-300"
850
+ onClick={(e) => e.stopPropagation()}
851
+ >
852
+ <MoreVerticalIcon size={iconSize.smallest}/>
853
+ </IconButton>
854
+ }
855
+ >
856
+ {rowActions.map(ra => (
857
+ <MenuItem
858
+ key={ra.collection.collectionSlug}
859
+ dense
860
+ onClick={() => {
861
+ sideEntityController.open({
862
+ path: ra.collection.collectionSlug,
863
+ entityId: ra.entityId,
864
+ collection: ra.collection.collection,
865
+ updateUrl: false
866
+ });
867
+ }}
868
+ >
869
+ {t("studio_sql_edit_entity", { name: ra.collection.collection.name,
870
+ id: String(ra.entityId) })}
871
+ </MenuItem>
872
+ ))}
873
+ </Menu>
874
+ </div>
875
+ );
876
+ }
877
+
878
+ // Regular data cell
879
+ if (!rowData) return null;
880
+ const val = rowData[column.key];
881
+ const displayValue = typeof val === "object" && val !== null ? JSON.stringify(val) : String(val ?? "");
882
+ return (
883
+ <div className="px-4 py-1.5 h-full flex items-center whitespace-nowrap text-[13px] text-text-primary dark:text-text-primary-dark font-mono">
884
+ <div className="truncate flex-grow" title={displayValue}>
885
+ {displayValue === "" ? <span className="text-text-disabled dark:text-text-disabled-dark italic text-[11px]">NULL</span> : displayValue}
886
+ </div>
887
+ </div>
888
+ );
889
+ }}
890
+ />
891
+ </div>
892
+ </div>
893
+ )}
894
+
895
+ {!isRunning && result && resultView === "console" && (
896
+ <div className="flex-grow overflow-auto p-2 space-y-1 font-mono text-[12px]">
897
+ {result.console.length === 0 ? (
898
+ <Typography variant="caption" className="text-text-disabled p-2">No console output</Typography>
899
+ ) : (
900
+ result.console.map((entry, i) => (
901
+ <div
902
+ key={i}
903
+ className={cls(
904
+ "px-2 py-1 rounded flex items-start gap-2",
905
+ entry.type === "error" && "bg-red-50 dark:bg-red-950/30 text-red-700 dark:text-red-400",
906
+ entry.type === "warn" && "bg-amber-50 dark:bg-amber-950/30 text-amber-700 dark:text-amber-400",
907
+ entry.type === "log" && "text-text-primary dark:text-text-primary-dark",
908
+ entry.type === "info" && "text-blue-700 dark:text-blue-400"
909
+ )}
910
+ >
911
+ <span className="text-[10px] opacity-50 flex-shrink-0 mt-0.5">
912
+ {entry.type === "error" ? "❌" : entry.type === "warn" ? "⚠️" : entry.type === "info" ? "ℹ️" : "›"}
913
+ </span>
914
+ <span className="whitespace-pre-wrap break-words">
915
+ {entry.args.map(a => typeof a === "object" ? JSON.stringify(a, null, 2) : String(a)).join(" ")}
916
+ </span>
917
+ </div>
918
+ ))
919
+ )}
920
+
921
+ {result.value !== undefined && (
922
+ <div className={cls("px-2 py-1 mt-2 border-t pt-2", defaultBorderMixin)}>
923
+ <Typography variant="caption" className="text-text-disabled text-[10px] uppercase tracking-wider mb-1 block">Return Value</Typography>
924
+ <pre className="text-text-primary dark:text-text-primary-dark whitespace-pre-wrap break-words text-[12px]">
925
+ {formatJSON(result.value)}
926
+ </pre>
927
+ </div>
928
+ )}
929
+ </div>
930
+ )}
931
+ </div>
932
+
933
+ {/* Footer bar — matching SQL editor */}
934
+ {!isRunning && result && !result.error && resultView === "table" && tableData.data.length > 0 && (
935
+ <div className={cls("p-2 px-4 border-t bg-surface-50 dark:bg-surface-900 flex justify-between items-center shrink-0", defaultBorderMixin)}>
936
+ <div className="flex space-x-4">
937
+ <div className="flex items-center text-[11px]">
938
+ <span className="font-bold text-text-disabled dark:text-text-disabled-dark mr-2 uppercase tracking-tighter">{t("studio_sql_rows")}</span>
939
+ <span className="font-mono text-text-secondary dark:text-text-secondary-dark">{tableData.data.length}</span>
940
+ </div>
941
+ <div className="flex items-center text-[11px]">
942
+ <span className="font-bold text-text-disabled dark:text-text-disabled-dark mr-2 uppercase tracking-tighter">{t("studio_sql_time")}</span>
943
+ <span className="font-mono text-text-secondary dark:text-text-secondary-dark">{result.duration.toFixed(0)}ms</span>
944
+ </div>
945
+ </div>
946
+ <div className="flex gap-2 overflow-x-auto no-scrollbar items-center px-2">
947
+ <Button
948
+ size="small"
949
+ variant="text"
950
+ className="text-[10px] uppercase font-bold text-text-secondary dark:text-text-secondary-dark whitespace-nowrap"
951
+ onClick={handleExportMarkdown}
952
+ >
953
+ {t("studio_sql_copy_markdown")}
954
+ </Button>
955
+ <Button
956
+ size="small"
957
+ variant="text"
958
+ className="text-[10px] uppercase font-bold text-text-secondary dark:text-text-secondary-dark whitespace-nowrap"
959
+ onClick={exportResult}
960
+ >
961
+ {t("studio_sql_export_json")}
962
+ </Button>
963
+ <Button
964
+ size="small"
965
+ variant="text"
966
+ className="text-[10px] uppercase font-bold text-text-secondary dark:text-text-secondary-dark whitespace-nowrap"
967
+ onClick={handleExportCSV}
968
+ >
969
+ {t("studio_sql_export_csv")}
970
+ </Button>
971
+ </div>
972
+ </div>
973
+ )}
974
+ </div>
975
+ }
976
+ />
977
+ </div>
978
+ </div>
979
+ }
980
+ />
981
+ </div>
982
+
983
+ {/* SaveIcon snippet dialog */}
984
+ <Dialog open={showSaveDialog} onOpenChange={setShowSaveDialog}>
985
+ <DialogTitle>SaveIcon Snippet</DialogTitle>
986
+ <DialogContent>
987
+ <TextField
988
+ label="Snippet name"
989
+ value={snippetName}
990
+ onChange={(e) => setSnippetName(e.target.value)}
991
+ placeholder="e.g. List all products"
992
+ autoFocus
993
+ onKeyDown={(e) => {
994
+ if (e.key === "Enter") saveSnippet();
995
+ }}
996
+ />
997
+ </DialogContent>
998
+ <DialogActions>
999
+ <Button variant="text" onClick={() => setShowSaveDialog(false)}>Cancel</Button>
1000
+ <Button variant="filled" color="primary" onClick={saveSnippet} disabled={!snippetName.trim()}>Save</Button>
1001
+ </DialogActions>
1002
+ </Dialog>
1003
+ </div>
1004
+ );
1005
+ }
1006
+
1007
+ // ─── JSON Syntax Highlighting ────────────────────────────────────────
1008
+
1009
+ function JSONHighlight({ value }: { value: any }) {
1010
+ const json = formatJSON(value);
1011
+ const { mode } = useModeController();
1012
+
1013
+ return (
1014
+ <Highlight
1015
+ theme={mode === "dark" ? themes.vsDark : themes.github}
1016
+ code={json}
1017
+ language="json"
1018
+ >
1019
+ {({ style, tokens, getLineProps, getTokenProps }) => (
1020
+ <span style={{ ...style,
1021
+ backgroundColor: "transparent" }}>
1022
+ {tokens.map((line, i) => (
1023
+ <div key={i} {...getLineProps({ line })}>
1024
+ {line.map((token, key) => (
1025
+ <span key={key} {...getTokenProps({ token })}/>
1026
+ ))}
1027
+ </div>
1028
+ ))}
1029
+ </span>
1030
+ )}
1031
+ </Highlight>
1032
+ );
1033
+ }