@elyx-code/editor-ui 0.0.2

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 (452) hide show
  1. package/README.md +2 -0
  2. package/package.json +109 -0
  3. package/src/App.tsx +31 -0
  4. package/src/Router.tsx +115 -0
  5. package/src/__mocks__/defaultModuleMock.ts +1 -0
  6. package/src/__mocks__/fileMock.ts +1 -0
  7. package/src/__mocks__/styleMock.ts +1 -0
  8. package/src/assets/Clock-11.1s-18px.svg +16 -0
  9. package/src/assets/Clock-11.1s-28px.svg +16 -0
  10. package/src/assets/authentication.svg +1 -0
  11. package/src/assets/canvas-backdrop-0.png +0 -0
  12. package/src/assets/canvas-backdrop-1.png +0 -0
  13. package/src/assets/canvas-backdrop-2.png +0 -0
  14. package/src/assets/canvas-backdrop-3.png +0 -0
  15. package/src/assets/canvas-backdrop-4.png +0 -0
  16. package/src/assets/canvas-backdrop-5.png +0 -0
  17. package/src/assets/canvas-backdrop.png +0 -0
  18. package/src/assets/checkmark-animation.gif +0 -0
  19. package/src/assets/checkmark-animation.mp4 +0 -0
  20. package/src/assets/code-formatting/format-black.svg +6 -0
  21. package/src/assets/code-formatting/format-dark-grey.svg +6 -0
  22. package/src/assets/code-formatting/format-light-grey.svg +6 -0
  23. package/src/assets/code-formatting/format-white.svg +6 -0
  24. package/src/assets/code-formatting/inline-black.svg +5 -0
  25. package/src/assets/code-formatting/inline-dark-grey.svg +5 -0
  26. package/src/assets/code-formatting/inline-light-grey.svg +5 -0
  27. package/src/assets/code-formatting/inline-white.svg +5 -0
  28. package/src/assets/contained-logo-full-word.png +0 -0
  29. package/src/assets/cron-job-color.png +0 -0
  30. package/src/assets/cron-job.png +0 -0
  31. package/src/assets/database-table-color.png +0 -0
  32. package/src/assets/database-table.png +0 -0
  33. package/src/assets/datatype-icons/black/any.svg +1 -0
  34. package/src/assets/datatype-icons/black/binary.svg +1 -0
  35. package/src/assets/datatype-icons/black/boolean.svg +3 -0
  36. package/src/assets/datatype-icons/black/date-time.svg +3 -0
  37. package/src/assets/datatype-icons/black/definition-entity.svg +6 -0
  38. package/src/assets/datatype-icons/black/key-file.svg +1 -0
  39. package/src/assets/datatype-icons/black/list.svg +3 -0
  40. package/src/assets/datatype-icons/black/null.svg +3 -0
  41. package/src/assets/datatype-icons/black/number.svg +13 -0
  42. package/src/assets/datatype-icons/black/project.svg +12 -0
  43. package/src/assets/datatype-icons/black/sql-program.svg +2 -0
  44. package/src/assets/datatype-icons/black/text.svg +3 -0
  45. package/src/assets/datatype-icons/black/unknown.svg +3 -0
  46. package/src/assets/datatype-icons/black/uuid.svg +4 -0
  47. package/src/assets/datatype-icons/black/void.svg +1 -0
  48. package/src/assets/datatype-icons/dark-grey/any.svg +1 -0
  49. package/src/assets/datatype-icons/dark-grey/boolean.svg +3 -0
  50. package/src/assets/datatype-icons/dark-grey/date-time.svg +3 -0
  51. package/src/assets/datatype-icons/dark-grey/definition-entity.svg +6 -0
  52. package/src/assets/datatype-icons/dark-grey/list.svg +3 -0
  53. package/src/assets/datatype-icons/dark-grey/null.svg +3 -0
  54. package/src/assets/datatype-icons/dark-grey/number.svg +13 -0
  55. package/src/assets/datatype-icons/dark-grey/project.svg +12 -0
  56. package/src/assets/datatype-icons/dark-grey/sql-program.svg +2 -0
  57. package/src/assets/datatype-icons/dark-grey/text.svg +3 -0
  58. package/src/assets/datatype-icons/dark-grey/unknown.svg +3 -0
  59. package/src/assets/datatype-icons/dark-grey/uuid.svg +4 -0
  60. package/src/assets/datatype-icons/dark-grey/void.svg +1 -0
  61. package/src/assets/datatype-icons/light-grey/any.svg +1 -0
  62. package/src/assets/datatype-icons/light-grey/boolean.svg +3 -0
  63. package/src/assets/datatype-icons/light-grey/date-time.svg +3 -0
  64. package/src/assets/datatype-icons/light-grey/definition-entity.svg +6 -0
  65. package/src/assets/datatype-icons/light-grey/list.svg +3 -0
  66. package/src/assets/datatype-icons/light-grey/null.svg +3 -0
  67. package/src/assets/datatype-icons/light-grey/number.svg +13 -0
  68. package/src/assets/datatype-icons/light-grey/project.svg +12 -0
  69. package/src/assets/datatype-icons/light-grey/sql-program.svg +2 -0
  70. package/src/assets/datatype-icons/light-grey/text.svg +3 -0
  71. package/src/assets/datatype-icons/light-grey/unknown.svg +3 -0
  72. package/src/assets/datatype-icons/light-grey/uuid.svg +4 -0
  73. package/src/assets/datatype-icons/light-grey/void.svg +1 -0
  74. package/src/assets/edit.png +0 -0
  75. package/src/assets/execution.svg +13 -0
  76. package/src/assets/favicon.svg +14 -0
  77. package/src/assets/file-search.svg +1 -0
  78. package/src/assets/http-endpoint.png +0 -0
  79. package/src/assets/image-input-placeholder.png +0 -0
  80. package/src/assets/logo-full-word-white.png +0 -0
  81. package/src/assets/logo-full-word.png +0 -0
  82. package/src/assets/password.svg +85 -0
  83. package/src/assets/pencil.png +0 -0
  84. package/src/assets/publish-project-rich-icon-2.svg +1 -0
  85. package/src/assets/publish-project-rich-icon.svg +1 -0
  86. package/src/assets/relational-database.png +0 -0
  87. package/src/assets/resources.svg +3 -0
  88. package/src/assets/resume-icon-14px.png +0 -0
  89. package/src/assets/server.png +0 -0
  90. package/src/assets/small-status/checkmark.svg +4 -0
  91. package/src/assets/small-status/error.svg +4 -0
  92. package/src/assets/small-status/loading.svg +4 -0
  93. package/src/assets/small-status/skipped.svg +11 -0
  94. package/src/assets/sql-connection-config.svg +1 -0
  95. package/src/assets/sql-row-transformer.svg +1 -0
  96. package/src/assets/ssl-certificate-config.svg +1 -0
  97. package/src/assets/sync.svg +1 -0
  98. package/src/assets/testing-logic-icon.svg +1 -0
  99. package/src/assets/versions.svg +25 -0
  100. package/src/assets/visual-programming-icon.svg +1 -0
  101. package/src/assets/warning-sign-24px.png +0 -0
  102. package/src/auth/index.ts +318 -0
  103. package/src/components/DialogLoader.tsx +94 -0
  104. package/src/components/EntityDialogHeader.tsx +110 -0
  105. package/src/components/EntityDialogSectionHeader.tsx +214 -0
  106. package/src/components/GalleryAddExternalIntegrationInfoDialog.tsx +87 -0
  107. package/src/components/GenerateProjectStartingLogicPromptDialog.tsx +281 -0
  108. package/src/components/LegacyRouteRedirector.tsx +55 -0
  109. package/src/components/ProPlanChip.tsx +23 -0
  110. package/src/components/ReportBugDialog.tsx +412 -0
  111. package/src/components/RequestIntegrationAccessDialog.tsx +261 -0
  112. package/src/components/UseTemplateProjectDialog.tsx +193 -0
  113. package/src/components/WorkspaceLayout.tsx +152 -0
  114. package/src/components/animated-svg/AnimatedCheckmark.tsx +41 -0
  115. package/src/components/animated-svg/AnimatedCrossmark.tsx +51 -0
  116. package/src/components/animated-svg/AnimatedEmailSending.tsx +38 -0
  117. package/src/components/animated-svg/AnimatedLoading.tsx +72 -0
  118. package/src/components/animated-svg/animated-svg.css +239 -0
  119. package/src/components/canvas/Canvas.tsx +16 -0
  120. package/src/components/canvas/CreateEntityMenu.tsx +2020 -0
  121. package/src/components/canvas/canvas.css +10 -0
  122. package/src/components/canvas/create-entity-menu.css +579 -0
  123. package/src/components/canvas-search/CanvasSearch.tsx +501 -0
  124. package/src/components/canvas-search/canvas-search.css +126 -0
  125. package/src/components/canvas-settings-menu/CanvasSettingsMenuButton.tsx +515 -0
  126. package/src/components/canvas-settings-menu/canvas-settings-menu.css +96 -0
  127. package/src/components/circular-image-upload/CircularImageUpload.tsx +113 -0
  128. package/src/components/circular-image-upload/circular-image-upload.css +69 -0
  129. package/src/components/costs/CostsDialog.tsx +459 -0
  130. package/src/components/data-type/DataTypeBuilder.tsx +3127 -0
  131. package/src/components/data-type/data-type-builder.css +45 -0
  132. package/src/components/dialogs/BetaAcknowledgeDialog.tsx +43 -0
  133. package/src/components/dialogs/ComplexDataDialog.tsx +458 -0
  134. package/src/components/dialogs/CronBuilderDialog.tsx +2145 -0
  135. package/src/components/dialogs/ExternalIntegrationConnections.tsx +565 -0
  136. package/src/components/dialogs/JsonEditorDialog.tsx +1392 -0
  137. package/src/components/dialogs/StringEditorDialog.tsx +268 -0
  138. package/src/components/dialogs/argument-declaration/ArgumentDeclaration.tsx +1167 -0
  139. package/src/components/dialogs/argument-declaration/ArgumentDeclarationDialogContent.tsx +128 -0
  140. package/src/components/dialogs/beta-dialog.css +165 -0
  141. package/src/components/dialogs/condition/Condition.tsx +431 -0
  142. package/src/components/dialogs/condition/ConditionDialogContent.tsx +126 -0
  143. package/src/components/dialogs/definition-entity/DefinitionEntityDialogContent.tsx +973 -0
  144. package/src/components/dialogs/function-call/FunctionCall.tsx +442 -0
  145. package/src/components/dialogs/function-call/FunctionCallDialogContent.tsx +126 -0
  146. package/src/components/dialogs/function-declaration/FunctionDeclaration.tsx +926 -0
  147. package/src/components/dialogs/function-declaration/FunctionDeclarationDialogContent.tsx +124 -0
  148. package/src/components/dialogs/generating-project-starting-logic-overlay/GeneratingProjectStartingLogicOverlay.tsx +176 -0
  149. package/src/components/dialogs/generating-project-starting-logic-overlay/generating-project-starting-logic-overlay.css +13 -0
  150. package/src/components/dialogs/global-event/GlobalEvent.tsx +475 -0
  151. package/src/components/dialogs/global-event/GlobalEventDialogContent.tsx +126 -0
  152. package/src/components/dialogs/help/HelpDialog.tsx +217 -0
  153. package/src/components/dialogs/help/HelpDilalogHomeContent.tsx +178 -0
  154. package/src/components/dialogs/help/help-dialog.css +116 -0
  155. package/src/components/dialogs/help/help-icon/HelpIconButton.tsx +41 -0
  156. package/src/components/dialogs/help/help-icon/help-icon.css +9 -0
  157. package/src/components/dialogs/input-map/InputMap.tsx +635 -0
  158. package/src/components/dialogs/input-map/InputMapDialogContent.tsx +126 -0
  159. package/src/components/dialogs/json-editor-dialog.css +4 -0
  160. package/src/components/dialogs/loop/Loop.tsx +650 -0
  161. package/src/components/dialogs/loop/LoopDialogContent.tsx +122 -0
  162. package/src/components/dialogs/operation/Operation.tsx +440 -0
  163. package/src/components/dialogs/operation/OperationDialogContent.tsx +126 -0
  164. package/src/components/dialogs/output-map/OutputMap.tsx +536 -0
  165. package/src/components/dialogs/output-map/OutputMapDialogContent.tsx +126 -0
  166. package/src/components/dialogs/property/Property.tsx +1490 -0
  167. package/src/components/dialogs/property/PropertyDialogContent.tsx +106 -0
  168. package/src/components/dialogs/search-statement/ColumnSelector.tsx +334 -0
  169. package/src/components/dialogs/search-statement/ConditionBuilder.tsx +750 -0
  170. package/src/components/dialogs/search-statement/DataAggregationSection.tsx +621 -0
  171. package/src/components/dialogs/search-statement/DataSourceSelection.tsx +734 -0
  172. package/src/components/dialogs/search-statement/EntityMetadataSection.tsx +135 -0
  173. package/src/components/dialogs/search-statement/FilterConditionsSection.tsx +151 -0
  174. package/src/components/dialogs/search-statement/InlineInputMap.tsx +153 -0
  175. package/src/components/dialogs/search-statement/LiteralValue.tsx +616 -0
  176. package/src/components/dialogs/search-statement/MainSourceAndInputsSection.tsx +271 -0
  177. package/src/components/dialogs/search-statement/NestedSearchStatementBuilder.tsx +170 -0
  178. package/src/components/dialogs/search-statement/OutputFormatSection.tsx +1779 -0
  179. package/src/components/dialogs/search-statement/ResultsSection.tsx +344 -0
  180. package/src/components/dialogs/search-statement/SearchStatementBuilder.tsx +251 -0
  181. package/src/components/dialogs/search-statement/SearchStatementDialogContent.tsx +398 -0
  182. package/src/components/dialogs/search-statement/ValueSelector.tsx +766 -0
  183. package/src/components/dialogs/search-statement/search-statement-context.tsx +1630 -0
  184. package/src/components/dialogs/search-statement/search-statement-dialog.css +56 -0
  185. package/src/components/dialogs/search-statement/test.sql +111 -0
  186. package/src/components/dialogs/value-descriptor/ValueDescriptor.tsx +824 -0
  187. package/src/components/dialogs/value-descriptor/ValueDescriptorDialogContent.tsx +124 -0
  188. package/src/components/dialogs/variable-declaration/VariableDeclaration.tsx +836 -0
  189. package/src/components/dialogs/variable-declaration/VariableDeclarationDialogContent.tsx +106 -0
  190. package/src/components/dialogs/variable-instance/VariableInstance.tsx +443 -0
  191. package/src/components/dialogs/variable-instance/VariableInstanceDialogContent.tsx +124 -0
  192. package/src/components/draggable-entity-card/ArgumentDeclaration.tsx +736 -0
  193. package/src/components/draggable-entity-card/CollapseEntityButton.tsx +170 -0
  194. package/src/components/draggable-entity-card/ConditionCard.tsx +1062 -0
  195. package/src/components/draggable-entity-card/ConnectionDeleteButton.tsx +309 -0
  196. package/src/components/draggable-entity-card/DataTypeIcon.tsx +624 -0
  197. package/src/components/draggable-entity-card/DraggableEntityCard.tsx +617 -0
  198. package/src/components/draggable-entity-card/ErrorMapProperty.tsx +464 -0
  199. package/src/components/draggable-entity-card/EventCard.tsx +700 -0
  200. package/src/components/draggable-entity-card/ExecutionInProgressValue.tsx +327 -0
  201. package/src/components/draggable-entity-card/FunctionDeclarationCard.tsx +819 -0
  202. package/src/components/draggable-entity-card/InputMapProperty.tsx +1067 -0
  203. package/src/components/draggable-entity-card/InternalCall.tsx +978 -0
  204. package/src/components/draggable-entity-card/InternalCallExecutionNode.tsx +643 -0
  205. package/src/components/draggable-entity-card/LogicScopeCallerNode.tsx +262 -0
  206. package/src/components/draggable-entity-card/LoopCard.tsx +791 -0
  207. package/src/components/draggable-entity-card/MainValueInput.tsx +523 -0
  208. package/src/components/draggable-entity-card/MainValueOutput.tsx +458 -0
  209. package/src/components/draggable-entity-card/MethodDeclaration.tsx +1088 -0
  210. package/src/components/draggable-entity-card/NestedCondition.tsx +1025 -0
  211. package/src/components/draggable-entity-card/OutputMapProperty.tsx +843 -0
  212. package/src/components/draggable-entity-card/PassthroughEntityCard.tsx +1247 -0
  213. package/src/components/draggable-entity-card/ReturnedError.tsx +549 -0
  214. package/src/components/draggable-entity-card/SmallSuccessFailureNodes.tsx +523 -0
  215. package/src/components/draggable-entity-card/SuccessFailureNodes.tsx +509 -0
  216. package/src/components/draggable-entity-card/TestEntityButton.tsx +946 -0
  217. package/src/components/draggable-entity-card/TestMenu.tsx +523 -0
  218. package/src/components/draggable-entity-card/TestMenuValidationDropdown.tsx +84 -0
  219. package/src/components/draggable-entity-card/UnreachableMarker.tsx +114 -0
  220. package/src/components/draggable-entity-card/VariableCard.tsx +1577 -0
  221. package/src/components/draggable-entity-card/VariableScopeMarker.tsx +117 -0
  222. package/src/components/draggable-entity-card/collapse-entity-button.css +44 -0
  223. package/src/components/draggable-entity-card/definition-entity/DefinitionEntityCard.tsx +1181 -0
  224. package/src/components/draggable-entity-card/definition-entity/DefinitionEntityIcon.tsx +36 -0
  225. package/src/components/draggable-entity-card/definition-entity/DefinitionEntityProperty.tsx +478 -0
  226. package/src/components/draggable-entity-card/definition-entity/DynamicFooterActions.tsx +112 -0
  227. package/src/components/draggable-entity-card/definition-entity/actions/external-integration-connection/ExportCredentialsFooterAction.tsx +461 -0
  228. package/src/components/draggable-entity-card/definition-entity/actions/external-integration-connection/RestablishConnectionFooterAction.tsx +199 -0
  229. package/src/components/draggable-entity-card/definition-entity/actions/external-integration-connection/restablish-connection-footer-action.css +85 -0
  230. package/src/components/draggable-entity-card/definition-entity/actions/google-drive/GoogleDriveFilePickerAPIFooterAction.tsx +277 -0
  231. package/src/components/draggable-entity-card/definition-entity/actions/google-drive/google-drive-file-picker-api-footer-action.css +107 -0
  232. package/src/components/draggable-entity-card/definition-entity/actions/persisted-entity/DatabaseFooterAction.tsx +452 -0
  233. package/src/components/draggable-entity-card/definition-entity/actions/persisted-entity/database-footer-action.css +86 -0
  234. package/src/components/draggable-entity-card/definition-entity/definition-entity-card.css +17 -0
  235. package/src/components/draggable-entity-card/draggable-entity-card.css +1140 -0
  236. package/src/components/draggable-entity-card/entity-locked-icon/EntityLockedIcon.tsx +133 -0
  237. package/src/components/draggable-entity-card/entity-locked-icon/entity-locked.css +8 -0
  238. package/src/components/draggable-entity-card/expand-properties-icon-button/ExpandPropertiesIconButton.tsx +84 -0
  239. package/src/components/draggable-entity-card/expand-properties-icon-button/expand-properties-icon-button.css +21 -0
  240. package/src/components/draggable-entity-card/implement-entity-icon/ImplementEntityIcon.tsx +74 -0
  241. package/src/components/draggable-entity-card/implement-entity-icon/implement-entity-icon.css +13 -0
  242. package/src/components/draggable-entity-card/logic-error/LogicErrorIconMenu.tsx +424 -0
  243. package/src/components/draggable-entity-card/logic-error/logic-error.css +23 -0
  244. package/src/components/draggable-entity-card/new-card-input-button/NewCardInputButton.tsx +193 -0
  245. package/src/components/draggable-entity-card/new-card-input-button/NewDynamicInputButton.tsx +214 -0
  246. package/src/components/draggable-entity-card/new-card-input-button/new-card-input-button.css +71 -0
  247. package/src/components/draggable-entity-card/new-card-output-button/NewCardOutputButton.tsx +192 -0
  248. package/src/components/draggable-entity-card/new-card-output-button/new-card-output-button.css +71 -0
  249. package/src/components/draggable-entity-card/termination-statement/TerminationStatementCard.tsx +1543 -0
  250. package/src/components/draggable-entity-card/termination-statement/termination-statement-card.css +17 -0
  251. package/src/components/draggable-entity-card/test-entity-button.css +55 -0
  252. package/src/components/draggable-entity-card/test-menu.css +181 -0
  253. package/src/components/draggable-entity-card/unreachable-marker.css +43 -0
  254. package/src/components/draggable-entity-card/variable-scope-marker.css +22 -0
  255. package/src/components/dynamic-value/DynamicValue.tsx +2395 -0
  256. package/src/components/dynamic-value/DynamicValueEntry.tsx +1957 -0
  257. package/src/components/dynamic-value/dynamic-value.css +230 -0
  258. package/src/components/editor/ElyxMonacoEditor.tsx +38 -0
  259. package/src/components/entity-error/EntityErrorListItem.tsx +47 -0
  260. package/src/components/entity-error/entity-error.css +198 -0
  261. package/src/components/entity-icon/EntityIcon.tsx +292 -0
  262. package/src/components/entity-icon/entity-icon.css +39 -0
  263. package/src/components/gallery-card/CreateNewProject.tsx +222 -0
  264. package/src/components/gallery-card/GalleryCard.tsx +171 -0
  265. package/src/components/gallery-card/MarketplaceCard.tsx +87 -0
  266. package/src/components/gallery-card/ProjectDuplicationCard.tsx +575 -0
  267. package/src/components/gallery-card/gallery-card.css +25 -0
  268. package/src/components/notifications/NotificationsIconButton.tsx +124 -0
  269. package/src/components/notifications/NotificationsPanel.tsx +385 -0
  270. package/src/components/notifications/notifications.css +189 -0
  271. package/src/components/online-users/LocalOnlineUsers.tsx +175 -0
  272. package/src/components/online-users/PageOnlineUsers.tsx +297 -0
  273. package/src/components/online-users/online-users.css +72 -0
  274. package/src/components/page-backdrop/PageBackdrop.tsx +8 -0
  275. package/src/components/page-backdrop/page-backdrop.css +7 -0
  276. package/src/components/project-configuration/DeleteProjectConfirmationDialog.tsx +134 -0
  277. package/src/components/project-configuration/ProjectConfigurationDialog.tsx +972 -0
  278. package/src/components/project-configuration/ProjectDataForm.tsx +121 -0
  279. package/src/components/project-configuration/UnpublishProjectConfirmationDialog.tsx +162 -0
  280. package/src/components/project-configuration/project-configuration-content.css +209 -0
  281. package/src/components/project-name/ProjectName.tsx +2025 -0
  282. package/src/components/project-name/project-name.css +599 -0
  283. package/src/components/publishing/Publication.tsx +133 -0
  284. package/src/components/publishing/history/PublicationHistoryContent.tsx +414 -0
  285. package/src/components/publishing/history/PublicationHistoryDialog.tsx +234 -0
  286. package/src/components/publishing/preview/PublicationPreviewDialog.tsx +1158 -0
  287. package/src/components/publishing/preview/PublishingPriceForecast.tsx +160 -0
  288. package/src/components/publishing/preview/PublishingResourcesDetails.tsx +91 -0
  289. package/src/components/publishing/publication-sequence/PublishingSequenceContent.tsx +375 -0
  290. package/src/components/publishing/publication-sequence/PublishingSequenceDialog.tsx +344 -0
  291. package/src/components/publishing/publishing-dialog.css +142 -0
  292. package/src/components/publishing/utils.ts +227 -0
  293. package/src/components/resources/ResourcesDialog.tsx +591 -0
  294. package/src/components/resources/UpgradeBanner.tsx +102 -0
  295. package/src/components/resources/codebase/CodebaseDetails.tsx +156 -0
  296. package/src/components/resources/cron-job/CronJobsList.tsx +532 -0
  297. package/src/components/resources/functions/FunctionsList.tsx +454 -0
  298. package/src/components/resources/http-api/HttpAPI.tsx +566 -0
  299. package/src/components/resources/http-api/HttpAPIClientModule.tsx +37 -0
  300. package/src/components/resources/logs/LogsViewer.tsx +768 -0
  301. package/src/components/resources/query.ts +74 -0
  302. package/src/components/resources/relational-database/DatabaseTable.tsx +905 -0
  303. package/src/components/resources/relational-database/RelationalDatabase.tsx +83 -0
  304. package/src/components/resources/relational-database/RelationalDatabaseSecrets.tsx +361 -0
  305. package/src/components/resources/resources-dialog.css +74 -0
  306. package/src/components/test-relational-database/DatabaseTable.tsx +913 -0
  307. package/src/components/test-relational-database/TestDatabaseDialogContent.tsx +670 -0
  308. package/src/components/test-relational-database/query.ts +74 -0
  309. package/src/components/toolbar/ToolBar.tsx +236 -0
  310. package/src/components/toolbar/toolbar.css +78 -0
  311. package/src/components/transaction-history/TransactionHistoryDialog.tsx +268 -0
  312. package/src/components/user/CurrentUserAvatar.tsx +65 -0
  313. package/src/components/user/UserChip.tsx +62 -0
  314. package/src/components/user/user.css +39 -0
  315. package/src/components/user-profile/ChangePasswordForm.tsx +67 -0
  316. package/src/components/user-profile/OwnUserProfileContent.tsx +665 -0
  317. package/src/components/user-profile/PublicUserProfileContent.tsx +99 -0
  318. package/src/components/user-profile/UserDataForm.tsx +75 -0
  319. package/src/components/user-profile/UserProfileDialog.tsx +110 -0
  320. package/src/components/user-profile/user-profile-content.css +25 -0
  321. package/src/config.ts +130 -0
  322. package/src/globals.d.ts +13 -0
  323. package/src/index.html +27 -0
  324. package/src/index.tsx +23 -0
  325. package/src/lib/badge/Badge.tsx +35 -0
  326. package/src/lib/badge/badge.css +32 -0
  327. package/src/lib/button/Button.tsx +129 -0
  328. package/src/lib/button/button.css +145 -0
  329. package/src/lib/canvas/canvas-undo-redo.ts +263 -0
  330. package/src/lib/canvas/defs.ts +170 -0
  331. package/src/lib/canvas/index.test.ts +189 -0
  332. package/src/lib/canvas/index.ts +6999 -0
  333. package/src/lib/canvas/utils.ts +59 -0
  334. package/src/lib/card/Card.tsx +62 -0
  335. package/src/lib/card/LoadingCard.tsx +82 -0
  336. package/src/lib/card/card.css +259 -0
  337. package/src/lib/chip/Chip.tsx +79 -0
  338. package/src/lib/chip/chip.css +0 -0
  339. package/src/lib/dialog/Dialog.tsx +122 -0
  340. package/src/lib/dialog/SmallDialog.tsx +61 -0
  341. package/src/lib/dialog/dialog.css +40 -0
  342. package/src/lib/display-data-structure/index.tsx +21 -0
  343. package/src/lib/dropdown/CanvasDropdownMenuCard.tsx +68 -0
  344. package/src/lib/dropdown/CanvasDropdownMenuCardOption.tsx +136 -0
  345. package/src/lib/dropdown/DropdownButton.tsx +104 -0
  346. package/src/lib/dropdown/DropdownMenuCard.tsx +324 -0
  347. package/src/lib/dropdown/DropdownMenuPopup.tsx +27 -0
  348. package/src/lib/dropdown/dropdown-button.css +76 -0
  349. package/src/lib/dropdown/dropdown-menu.css +151 -0
  350. package/src/lib/json-editor/RawJsonEditor.tsx +137 -0
  351. package/src/lib/json-editor/json-editor.css +35 -0
  352. package/src/lib/loader/Loader.tsx +120 -0
  353. package/src/lib/loader/loader.css +38 -0
  354. package/src/lib/pagination/Pagination.tsx +64 -0
  355. package/src/lib/popup/CanvasPopupBaseComponent.tsx +103 -0
  356. package/src/lib/popup/Popup.tsx +243 -0
  357. package/src/lib/popup/popup.css +16 -0
  358. package/src/lib/table/RowForm.tsx +301 -0
  359. package/src/lib/table/Table.tsx +1069 -0
  360. package/src/lib/table/table.css +249 -0
  361. package/src/lib/table/types.ts +108 -0
  362. package/src/lib/text-area/TextArea.tsx +183 -0
  363. package/src/lib/text-area/text-area.css +156 -0
  364. package/src/lib/text-field/TextField.tsx +218 -0
  365. package/src/lib/text-field/index.ts +8 -0
  366. package/src/lib/text-field/text-field.css +201 -0
  367. package/src/lib/tooltip/Tooltip.tsx +24 -0
  368. package/src/lib/tooltip/tooltip.css +17 -0
  369. package/src/localization/index.ts +47 -0
  370. package/src/main.css +343 -0
  371. package/src/pages/Auth.tsx +848 -0
  372. package/src/pages/Editor.tsx +883 -0
  373. package/src/pages/ErrorPage.tsx +179 -0
  374. package/src/pages/Gallery.tsx +1693 -0
  375. package/src/pages/NewPaymentMethodCallback.tsx +53 -0
  376. package/src/pages/NotFoundPage.tsx +126 -0
  377. package/src/pages/PricingPlans.tsx +155 -0
  378. package/src/pages/auth.css +304 -0
  379. package/src/pages/gallery.css +421 -0
  380. package/src/payments/index.ts +187 -0
  381. package/src/popup-notification/index.ts +90 -0
  382. package/src/services/database/index.ts +1 -0
  383. package/src/services/database/utils.ts +1301 -0
  384. package/src/services/editor/CanvasElement.tsx +2934 -0
  385. package/src/services/editor/CanvasElementConnectionDeleteButton.ts +204 -0
  386. package/src/services/editor/CanvasPopup.tsx +749 -0
  387. package/src/services/editor/EditorService.ts +8157 -0
  388. package/src/services/editor/area.ts +1312 -0
  389. package/src/services/editor/connections.ts +1019 -0
  390. package/src/services/editor/create/condition.ts +25 -0
  391. package/src/services/editor/create/definition-entity.ts +29 -0
  392. package/src/services/editor/create/function-call.ts +25 -0
  393. package/src/services/editor/create/global-event.ts +33 -0
  394. package/src/services/editor/create/loop.ts +25 -0
  395. package/src/services/editor/create/operation.ts +30 -0
  396. package/src/services/editor/create/utils.ts +140 -0
  397. package/src/services/editor/create/variable-declaration.ts +135 -0
  398. package/src/services/editor/create/variable-instance.ts +100 -0
  399. package/src/services/editor/editor-ui-extensions-context.ts +43 -0
  400. package/src/services/editor/entities-metadata.json +9310 -0
  401. package/src/services/editor/icons.ts +1093 -0
  402. package/src/services/editor/index.ts +1 -0
  403. package/src/services/editor/layout.ts +102 -0
  404. package/src/services/editor/modules/built-in-function-implementations/base.ts +14 -0
  405. package/src/services/editor/modules/built-in-function-implementations/create-persisted-entity/index.ts +56 -0
  406. package/src/services/editor/modules/built-in-function-implementations/delete-persisted-entity/index.ts +55 -0
  407. package/src/services/editor/modules/built-in-function-implementations/index.ts +4 -0
  408. package/src/services/editor/modules/built-in-function-implementations/update-persisted-entity/index.ts +56 -0
  409. package/src/services/editor/modules/operations-implementations/external-integrations/google-drive/get-files.ts +183 -0
  410. package/src/services/editor/modules/operations-implementations/external-integrations/google-drive/list-drives.ts +124 -0
  411. package/src/services/editor/modules/operations-implementations/external-integrations/google-drive/list-root-folders.ts +125 -0
  412. package/src/services/editor/modules/operations-implementations/external-integrations/google-drive/smart-fetch-document.ts +702 -0
  413. package/src/services/editor/modules/operations-implementations/external-integrations/google-drive/upload-document.ts +535 -0
  414. package/src/services/editor/modules/operations-implementations/external-integrations/google-gemini/generate-content.ts +193 -0
  415. package/src/services/editor/modules/operations-implementations/external-integrations/google-mail/get-emails.ts +586 -0
  416. package/src/services/editor/modules/operations-implementations/external-integrations/google-mail/send-email.ts +386 -0
  417. package/src/services/editor/modules/operations-implementations/external-integrations/index.ts +12 -0
  418. package/src/services/editor/modules/operations-implementations/external-integrations/slack/channels.ts +240 -0
  419. package/src/services/editor/modules/operations-implementations/external-integrations/slack/messages.ts +210 -0
  420. package/src/services/editor/modules/operations-implementations/external-integrations/slack/replies.ts +200 -0
  421. package/src/services/editor/modules/operations-implementations/external-integrations/slack/send-message.ts +177 -0
  422. package/src/services/editor/modules/operations-implementations/index.ts +1 -0
  423. package/src/services/editor/modules/search-node-implementation/index.ts +42 -0
  424. package/src/services/editor/modules/sql-migrations-generation.tsx +1054 -0
  425. package/src/services/editor/publication/publication.ts +578 -0
  426. package/src/services/editor/ui.ts +1348 -0
  427. package/src/services/editor/utils.ts +5868 -0
  428. package/src/services/editor/value-store.ts +619 -0
  429. package/src/services/execution/built-in-function-implementations.ts +422 -0
  430. package/src/services/execution/index.ts +4747 -0
  431. package/src/services/execution/logic.ts +121 -0
  432. package/src/services/execution/test-instance.tsx +2296 -0
  433. package/src/services/execution/utils.ts +33 -0
  434. package/src/services/execution/value-resolution.test.ts +424 -0
  435. package/src/services/execution/value-resolution.ts +4087 -0
  436. package/src/services/integrations/ExternalIntegrationsService.ts +439 -0
  437. package/src/services/integrations/api.ts +175 -0
  438. package/src/services/local-relational-database/idb_helper.ts +66 -0
  439. package/src/services/local-relational-database/index.ts +3308 -0
  440. package/src/services/local-relational-database/utils.ts +403 -0
  441. package/src/services/notifications/index.ts +525 -0
  442. package/src/services/user/index.ts +144 -0
  443. package/src/setupTests.ts +1 -0
  444. package/src/socket/socket.ts +248 -0
  445. package/src/socket/utils.ts +10 -0
  446. package/src/store/workspace.ts +12 -0
  447. package/src/theme.ts +19 -0
  448. package/src/utils/DOM.ts +39 -0
  449. package/src/utils/date.ts +169 -0
  450. package/src/utils/index.ts +158 -0
  451. package/src/utils/react.tsx +679 -0
  452. package/src/utils/testing.ts +103 -0
@@ -0,0 +1,3308 @@
1
+ import {
2
+ BUILT_IN_BASE_ENTITY_IDS,
3
+ BaseEntityNames,
4
+ EntityType,
5
+ ProjectStateEvents,
6
+ SearchState,
7
+ checkHasBaseEntity,
8
+ checkIsEmptyValue,
9
+ getColumnProperties,
10
+ getDatabaseEntity,
11
+ lowercaseFirstLetter,
12
+ resolvePersistedDefinitionEntityDatabaseEntity,
13
+ searchStatementDefs,
14
+ PrimitiveTypes,
15
+ InputMapState,
16
+ DefinitionEntityState,
17
+ PropertyState,
18
+ resolveEntityName,
19
+ toCamelCase,
20
+ toPascalCase,
21
+ SQLAST as SQLASTLib,
22
+ searchStatementState as searchStatementStateLib,
23
+ searchStatementUtils
24
+ } from '@elyx-code/project-logic-tree';
25
+ import {
26
+ Connection as BaseConnection,
27
+ IDataBase,
28
+ IInsertQuery,
29
+ IOrderQuery,
30
+ IRemoveQuery,
31
+ ISelectQuery,
32
+ ITable,
33
+ IUpdateQuery
34
+ } from 'jsstore';
35
+ import {
36
+ persistedDefinitionEntityToDBTableConfig,
37
+ resolveForeignKeyInfo
38
+ } from './utils';
39
+ import { initLocalDBConnection, initLocalIndexedDatabase } from './idb_helper';
40
+ import {
41
+ fromOrderByClauseToJsStore,
42
+ fromWhereClauseToJsStoreWhereObject,
43
+ getJoinClausesFromSelectStatement,
44
+ getAllSelectedColumnNamesFromSelectClause
45
+ } from '../database';
46
+ import { EditorService } from '../editor';
47
+ import { IDynamicValue } from '../editor/value-store';
48
+
49
+ const { ColumnRef, WhereStatement, DataSource, AggregationStatement } =
50
+ searchStatementStateLib;
51
+
52
+ const {
53
+ SearchStatementNodeType,
54
+ AggregationStatementType,
55
+ SortStatementDirection,
56
+ WhereStatementOperator,
57
+ SQLFunctionCategory,
58
+ DataSourceType
59
+ } = searchStatementDefs;
60
+
61
+ type AggregationStatement = searchStatementStateLib.AggregationStatement;
62
+ type ColumnRef = searchStatementStateLib.ColumnRef;
63
+ type DataSource = searchStatementStateLib.DataSource;
64
+ type SearchStatementState = searchStatementStateLib.SearchStatementState;
65
+ type WhereStatement = searchStatementStateLib.WhereStatement;
66
+ type SearchNode = searchStatementStateLib.SearchNode;
67
+ type FunctionCall = searchStatementStateLib.FunctionCall;
68
+
69
+ type Program = SQLASTLib.Program;
70
+ type SelectStmt = SQLASTLib.SelectStmt;
71
+ type LimitClause = SQLASTLib.LimitClause;
72
+ type WhereStatementOperator = searchStatementDefs.WhereStatementOperator;
73
+ type SortStatementDirection = searchStatementDefs.SortStatementDirection;
74
+ type AggregationStatementType = searchStatementDefs.AggregationStatementType;
75
+
76
+ export async function describeExisting(
77
+ dbIdentifier: string
78
+ ): Promise<IDataBase | null> {
79
+ return new Promise((resolve, reject) => {
80
+ const request = indexedDB.open(dbIdentifier);
81
+
82
+ request.onsuccess = (event) => {
83
+ const db = (event.target as any).result;
84
+ // console.log('Object store names:', [...db.objectStoreNames]);
85
+
86
+ const finalDef: IDataBase = {
87
+ name: dbIdentifier,
88
+ tables: [] as ITable[],
89
+ version: db.version
90
+ };
91
+
92
+ // Iterate over each object store to inspect its properties
93
+ for (const storeName of db.objectStoreNames) {
94
+ const transaction = db.transaction(storeName, 'readonly');
95
+ const objectStore = transaction.objectStore(storeName);
96
+ // console.log(`Object store: ${storeName}`);
97
+ // console.log(' Key path:', objectStore.keyPath);
98
+ // console.log(' Auto increment:', objectStore.autoIncrement);
99
+ // console.log(' Indexes:', [...objectStore.indexNames]);
100
+
101
+ const columns: ITable['columns'] = {};
102
+
103
+ // Iterate over each index to inspect its properties
104
+ for (const indexName of objectStore.indexNames) {
105
+ const index = objectStore.index(indexName);
106
+ // console.log(` Index: ${indexName}`);
107
+ // console.log(' Key path:', index.keyPath);
108
+ // console.log(' Unique:', index.unique);
109
+
110
+ columns[indexName] = {
111
+ primaryKey: index.keyPath === objectStore.keyPath,
112
+ autoIncrement: index.autoIncrement,
113
+ unique: index.unique,
114
+ notNull: false,
115
+ dataType: 'string',
116
+ default: null,
117
+ multiEntry: false,
118
+ enableSearch: false,
119
+ keyPath: index.keyPath
120
+ };
121
+ }
122
+
123
+ finalDef.tables.push({
124
+ name: storeName,
125
+ columns: columns
126
+ });
127
+ }
128
+ db.close();
129
+ resolve(finalDef);
130
+ };
131
+
132
+ request.onerror = (event) => {
133
+ console.error(
134
+ 'Error opening database:',
135
+ (event.target as any).error
136
+ );
137
+ resolve(null);
138
+ };
139
+ });
140
+ }
141
+
142
+ export const toLower = SQLASTLib.cstVisitor({
143
+ keyword: (kw) => {
144
+ kw.text = kw.text.toLowerCase();
145
+ }
146
+ });
147
+
148
+ export interface Connection extends BaseConnection {
149
+ $sql: {
150
+ run: (sql: string) => Promise<any>;
151
+ };
152
+ }
153
+
154
+ export interface ISelectQueryResult {
155
+ columns: searchStatementUtils.IColumnMapping[];
156
+ rows: {
157
+ [columnName: string]: any;
158
+ }[];
159
+ tableName: string | null;
160
+ entity: DefinitionEntityState | null;
161
+ // Total number of rows in the table
162
+ total: number;
163
+ }
164
+
165
+ export interface ISelectQueryResultV2 {
166
+ columns: (ColumnRef | FunctionCall)[];
167
+ rows: {
168
+ [columnName: string]: any;
169
+ }[];
170
+ tableName: string | null;
171
+ entity: DefinitionEntityState | null;
172
+ // Total number of rows in the table
173
+ total: number;
174
+ }
175
+
176
+ export interface IDataBaseDetails {
177
+ items: Table[];
178
+ // Data for the initial query
179
+ data: ISelectQueryResult;
180
+ // Total number of rows in the database
181
+ total: number;
182
+ }
183
+
184
+ // This entity puts the table name and property entity that represents it in the canvas under one object
185
+ export class Column implements searchStatementUtils.IColumn {
186
+ columnName: string;
187
+ property: PropertyState;
188
+
189
+ // Table that this column class instance is stored in
190
+ table: Table;
191
+ // Table in which this column can actually be found in the latest version of the database
192
+ actualTable: Table;
193
+
194
+ constructor(columnName: string, property: PropertyState, table: Table) {
195
+ this.table = table;
196
+ this.columnName = columnName;
197
+ this.property = property;
198
+ }
199
+
200
+ get primaryKey(): boolean {
201
+ const builtInPrimaryKeyPropertyId =
202
+ BUILT_IN_BASE_ENTITY_IDS[EntityType.BuiltInBaseEntity][
203
+ BaseEntityNames.PERSISTED_ENTITY
204
+ ].properties.primaryKey.id;
205
+
206
+ const isPrimaryKey =
207
+ this.property.id === builtInPrimaryKeyPropertyId ||
208
+ !!this.property.implements.find(
209
+ (impl) => impl.id === builtInPrimaryKeyPropertyId
210
+ );
211
+
212
+ return isPrimaryKey;
213
+ }
214
+ }
215
+
216
+ // This entity puts the table name and entity that represents it in the canvas under one object
217
+ export class Table implements searchStatementUtils.ITable {
218
+ tableName: string;
219
+ entity: DefinitionEntityState;
220
+ columns: Column[] = [];
221
+
222
+ // Total number of rows in the table
223
+ total: number = 0;
224
+
225
+ // Database
226
+ database: Database;
227
+
228
+ constructor(
229
+ tableName: string,
230
+ entity: DefinitionEntityState,
231
+ database: Database
232
+ ) {
233
+ this.database = database;
234
+ this.tableName = tableName;
235
+ this.entity = entity;
236
+ }
237
+
238
+ async init(): Promise<void> {
239
+ await this.updateTotal();
240
+
241
+ const columnProperties = getColumnProperties(this.entity);
242
+
243
+ // Loop over properties and add one key for each in the 'columns' property of the table
244
+ columnProperties.forEach((property) => {
245
+ const columnName =
246
+ property.codeName ||
247
+ lowercaseFirstLetter(
248
+ toCamelCase(
249
+ resolveEntityName(property, this.entity.project)
250
+ )
251
+ );
252
+
253
+ const existingColumn = this.columns.find(
254
+ (column) => column.columnName === columnName
255
+ );
256
+
257
+ if (existingColumn) {
258
+ return;
259
+ }
260
+
261
+ this.columns.push(new Column(columnName, property, this));
262
+ });
263
+ }
264
+
265
+ // Gets the total number of rows in the table and sets it to the 'total' property
266
+ async updateTotal(): Promise<number> {
267
+ const oldTotal = this.total;
268
+
269
+ const rows = await this.database.select({
270
+ from: this.tableName
271
+ });
272
+
273
+ this.total = rows.length;
274
+
275
+ const difference = this.total - oldTotal;
276
+
277
+ // Add the difference to the total of the parent database
278
+ this.database.total += difference;
279
+
280
+ return this.total;
281
+ }
282
+ }
283
+
284
+ function areValuesEqual(left: any, right: any): boolean {
285
+ if (left === right) {
286
+ return true;
287
+ }
288
+ if (
289
+ left === null ||
290
+ left === undefined ||
291
+ right === null ||
292
+ right === undefined
293
+ ) {
294
+ return left === right;
295
+ }
296
+ // Numeric coercion for comparison
297
+ if (typeof left === 'number' && typeof right === 'string') {
298
+ const rightNum = Number(right);
299
+ return !isNaN(rightNum) && right !== '' && left === rightNum;
300
+ }
301
+ if (typeof left === 'string' && typeof right === 'number') {
302
+ const leftNum = Number(left);
303
+ return !isNaN(leftNum) && left !== '' && leftNum === right;
304
+ }
305
+ // Boolean-string coercion
306
+ if (typeof left === 'boolean' && typeof right === 'string') {
307
+ return String(left) === right;
308
+ }
309
+ if (typeof left === 'string' && typeof right === 'boolean') {
310
+ return left === String(right);
311
+ }
312
+ return false;
313
+ }
314
+
315
+ function coerceToComparable(left: any, right: any): [any, any] {
316
+ if (typeof left === 'number' && typeof right === 'string') {
317
+ const rightNum = Number(right);
318
+ if (!isNaN(rightNum) && right !== '') {
319
+ return [left, rightNum];
320
+ }
321
+ }
322
+ if (typeof left === 'string' && typeof right === 'number') {
323
+ const leftNum = Number(left);
324
+ if (!isNaN(leftNum) && left !== '') {
325
+ return [leftNum, right];
326
+ }
327
+ }
328
+ return [left, right];
329
+ }
330
+
331
+ function sanitizeValueForColumn(value: any, column: Column): any {
332
+ if (checkIsEmptyValue(value)) {
333
+ return null;
334
+ }
335
+ const dataType = column.property.dataType;
336
+ if (dataType?.entity?.type === EntityType.PrimitiveEntity) {
337
+ switch (dataType.entity.name) {
338
+ case PrimitiveTypes.Number: {
339
+ const num = Number(value);
340
+ return isNaN(num) ? null : num;
341
+ }
342
+ case PrimitiveTypes.Boolean:
343
+ return value === 'true' || value === true;
344
+ }
345
+ } else if (dataType?.entity?.type === EntityType.DefinitionEntity) {
346
+ const builtInPrimaryKeyPropertyId =
347
+ BUILT_IN_BASE_ENTITY_IDS[EntityType.BuiltInBaseEntity][
348
+ BaseEntityNames.PERSISTED_ENTITY
349
+ ].properties.primaryKey.id;
350
+
351
+ const referencedProperties = getColumnProperties(
352
+ dataType.entity as DefinitionEntityState
353
+ );
354
+ const primaryKeyProperty = referencedProperties.find((prop) => {
355
+ return (
356
+ prop.id === builtInPrimaryKeyPropertyId ||
357
+ !!prop.implements.find(
358
+ (impl) => impl.id === builtInPrimaryKeyPropertyId
359
+ )
360
+ );
361
+ });
362
+
363
+ if (primaryKeyProperty) {
364
+ const pkDataType = primaryKeyProperty.dataType;
365
+ if (
366
+ pkDataType?.entity?.type === EntityType.PrimitiveEntity &&
367
+ pkDataType.entity.name === PrimitiveTypes.Number
368
+ ) {
369
+ const num = Number(value);
370
+ return isNaN(num) ? null : num;
371
+ }
372
+ }
373
+ }
374
+ return value;
375
+ }
376
+
377
+ async function migrateAndCompactData(
378
+ dbName: string,
379
+ tablesToMigrate: {
380
+ tableName: string;
381
+ desiredColumnsMeta: { [colName: string]: any };
382
+ renames: { [old: string]: string };
383
+ }[]
384
+ ): Promise<void> {
385
+ return new Promise((resolve) => {
386
+ const request = indexedDB.open(dbName);
387
+ request.onerror = (event) => {
388
+ console.error(
389
+ 'Failed to open DB for data migration:',
390
+ (event.target as any).error
391
+ );
392
+ resolve();
393
+ };
394
+ request.onsuccess = (event) => {
395
+ const db = request.result;
396
+ const tableNames = tablesToMigrate.map((t) => t.tableName);
397
+
398
+ // If any table names are not in db.objectStoreNames, filter them out to prevent transaction errors
399
+ const validTableNames = tableNames.filter((name) =>
400
+ db.objectStoreNames.contains(name)
401
+ );
402
+ if (validTableNames.length === 0) {
403
+ db.close();
404
+ resolve();
405
+ return;
406
+ }
407
+
408
+ try {
409
+ const transaction = db.transaction(
410
+ validTableNames,
411
+ 'readwrite'
412
+ );
413
+
414
+ tablesToMigrate.forEach((tableInfo) => {
415
+ if (!db.objectStoreNames.contains(tableInfo.tableName)) {
416
+ return;
417
+ }
418
+ const store = transaction.objectStore(tableInfo.tableName);
419
+ const cursorRequest = store.openCursor();
420
+
421
+ cursorRequest.onsuccess = (e) => {
422
+ const cursor = (e.target as any).result;
423
+ if (cursor) {
424
+ const row = cursor.value;
425
+ let modified = false;
426
+
427
+ // 1. Process renames
428
+ Object.entries(tableInfo.renames).forEach(
429
+ ([oldCol, newCol]) => {
430
+ if (row[oldCol] !== undefined) {
431
+ row[newCol] = row[oldCol];
432
+ delete row[oldCol];
433
+ modified = true;
434
+ }
435
+ }
436
+ );
437
+
438
+ // 2. Compact: delete any fields not in the desired columns list
439
+ Object.keys(row).forEach((key) => {
440
+ if (!tableInfo.desiredColumnsMeta[key]) {
441
+ delete row[key];
442
+ modified = true;
443
+ }
444
+ });
445
+
446
+ // 2.5 Type Casting (e.g. String to Number for foreign keys)
447
+ Object.entries(
448
+ tableInfo.desiredColumnsMeta
449
+ ).forEach(([colName, colMeta]) => {
450
+ if (colMeta.dataType === 'number') {
451
+ if (typeof row[colName] === 'string') {
452
+ const parsed = Number(row[colName]);
453
+ if (!isNaN(parsed)) {
454
+ row[colName] = parsed;
455
+ modified = true;
456
+ } else if (row[colName] === '') {
457
+ row[colName] = null;
458
+ modified = true;
459
+ }
460
+ }
461
+ }
462
+ });
463
+
464
+ // 3. Drop rows that are incompatible with the new shape
465
+ let isCompatible = true;
466
+ Object.entries(
467
+ tableInfo.desiredColumnsMeta
468
+ ).forEach(([colName, colMeta]) => {
469
+ if (
470
+ colMeta.notNull &&
471
+ (colMeta.default === undefined ||
472
+ colMeta.default === null)
473
+ ) {
474
+ if (
475
+ row[colName] === undefined ||
476
+ row[colName] === null
477
+ ) {
478
+ isCompatible = false;
479
+ }
480
+ }
481
+ });
482
+
483
+ if (!isCompatible) {
484
+ cursor.delete();
485
+ } else if (modified) {
486
+ cursor.update(row);
487
+ }
488
+ cursor.continue();
489
+ }
490
+ };
491
+ });
492
+
493
+ transaction.oncomplete = () => {
494
+ db.close();
495
+ resolve();
496
+ };
497
+ transaction.onerror = (e) => {
498
+ console.error(
499
+ 'Transaction error in migrateAndCompactData:',
500
+ (e.target as any).error
501
+ );
502
+ db.close();
503
+ resolve();
504
+ };
505
+ } catch (err) {
506
+ console.error(
507
+ 'Error during migrateAndCompactData transaction creation:',
508
+ err
509
+ );
510
+ db.close();
511
+ resolve();
512
+ }
513
+ };
514
+ });
515
+ }
516
+
517
+ type PrimitiveRawResults =
518
+ | number
519
+ | string
520
+ | boolean
521
+ | null
522
+ | Date
523
+ | (number | string | boolean | null | Date)[];
524
+
525
+ type RawResults = PrimitiveRawResults | PrimitiveRawResults[] | RawResults[];
526
+
527
+ // This entity puts the connection to a real local database and the entity that owns it under one object
528
+ export class Database {
529
+ connection:
530
+ | (Connection & {
531
+ $sql: {
532
+ run: (sql: string) => Promise<any>;
533
+ };
534
+ } & {
535
+ // Missing while not fully initialized
536
+ database?: IDataBase;
537
+ })
538
+ | null = null;
539
+
540
+ entity: DefinitionEntityState;
541
+ tables: Table[] = [];
542
+
543
+ // Total number of rows in all tables
544
+ total: number = 0;
545
+
546
+ store: LocalRelationalDatabasesStore;
547
+
548
+ onGoingQueryCache: {
549
+ [query: string]: ISelectQueryResult;
550
+ } = {};
551
+
552
+ constructor(
553
+ entity: DefinitionEntityState,
554
+ store: LocalRelationalDatabasesStore
555
+ ) {
556
+ this.store = store;
557
+ this.entity = entity;
558
+
559
+ this.processSelectQuery = this.processSelectQuery.bind(this);
560
+ this.init = this.init.bind(this);
561
+ }
562
+
563
+ async destroy() {
564
+ try {
565
+ // Clear local storage mappings for this database's tables
566
+ const attachedTableEntities: DefinitionEntityState[] =
567
+ this.store.persistedEntities.filter((persistedEntity) => {
568
+ const databaseEntity = getDatabaseEntity(persistedEntity);
569
+ return (
570
+ !!databaseEntity &&
571
+ databaseEntity?.id === this.entity?.id
572
+ );
573
+ });
574
+
575
+ attachedTableEntities.forEach((tableEntity) => {
576
+ const mappingKey = `elyx__local_db_column_mappings__${tableEntity.project.id}__${tableEntity.id}`;
577
+ try {
578
+ localStorage.removeItem(mappingKey);
579
+ } catch (e) {
580
+ // Ignore
581
+ }
582
+ });
583
+
584
+ // If the database is not initialized, it will throw an error
585
+ // But it might actually exist in storage
586
+ // So we need to catch the error, re-initialize the connection and drop the database
587
+ await this.connection.dropDb();
588
+ } catch (e) {
589
+ // Ignore the error
590
+ console.log('Error dropping database:', e);
591
+ }
592
+
593
+ await this.disconnect();
594
+
595
+ // Remove the database from the store
596
+ this.store.databases = this.store.databases.filter(
597
+ (db) => db.entity.id !== this.entity.id
598
+ );
599
+ }
600
+
601
+ static toDbName(entity: DefinitionEntityState): string {
602
+ const pascalName = toPascalCase(entity.name);
603
+ return `elyx__project--${entity.project.id}__${pascalName}__indexdb`;
604
+ }
605
+
606
+ get definition(): IDataBase {
607
+ const attachedTableEntities: DefinitionEntityState[] =
608
+ this.store.persistedEntities.filter((persistedEntity) => {
609
+ // Some persisted entities aren't directly extending the base built in abstract persisted entity
610
+ // But rather, extend another definition entity that eventually down the extension inheritance extends a base built in persisted entity
611
+ // For that reason, we first find the base persisted entity
612
+ const databaseEntity = getDatabaseEntity(persistedEntity);
613
+
614
+ return (
615
+ !!databaseEntity && databaseEntity?.id === this.entity?.id
616
+ );
617
+ });
618
+
619
+ const tables = attachedTableEntities.map(
620
+ persistedDefinitionEntityToDBTableConfig
621
+ );
622
+
623
+ const dbName = Database.toDbName(this.entity);
624
+
625
+ const dataBaseDef: IDataBase = {
626
+ name: dbName,
627
+ tables: tables.map((table) => table.definition)
628
+ };
629
+
630
+ return dataBaseDef;
631
+ }
632
+
633
+ async init(): Promise<Database> {
634
+ // Find the persisted entities that belong to this db
635
+ // Persisted entities have a property called 'database' that points to the db entity
636
+ const attachedTableEntities: DefinitionEntityState[] =
637
+ this.store.persistedEntities.filter((persistedEntity) => {
638
+ // Some persisted entities aren't directly extending the base built in abstract persisted entity
639
+ // But rather, extend another definition entity that eventually down the extension inheritance extends a base built in persisted entity
640
+ // For that reason, we first find the base persisted entity
641
+ const databaseEntity = getDatabaseEntity(persistedEntity);
642
+
643
+ return (
644
+ !!databaseEntity && databaseEntity?.id === this.entity?.id
645
+ );
646
+ });
647
+
648
+ // Initialize the db with one table for each attached entity
649
+ const tables = attachedTableEntities.map(
650
+ persistedDefinitionEntityToDBTableConfig
651
+ );
652
+
653
+ const tablesToMigrate: {
654
+ tableName: string;
655
+ desiredColumnsMeta: { [colName: string]: any };
656
+ renames: { [old: string]: string };
657
+ }[] = [];
658
+
659
+ tables.forEach((table) => {
660
+ const mappingKey = `elyx__local_db_column_mappings__${table.entity.project.id}__${table.entity.id}`;
661
+ let savedMappings: { [propertyId: string]: string } = {};
662
+ try {
663
+ const rawMappings = localStorage.getItem(mappingKey);
664
+ if (rawMappings) {
665
+ savedMappings = JSON.parse(rawMappings);
666
+ }
667
+ } catch (e) {
668
+ console.error(
669
+ 'Failed to parse column mappings from localStorage:',
670
+ e
671
+ );
672
+ }
673
+
674
+ const currentProperties = getColumnProperties(table.entity);
675
+ const renames: { [oldCol: string]: string } = {};
676
+
677
+ currentProperties.forEach((prop) => {
678
+ const oldColumnName = savedMappings[prop.id];
679
+ const newColumnName =
680
+ prop.codeName ||
681
+ lowercaseFirstLetter(
682
+ toCamelCase(
683
+ resolveEntityName(prop, table.entity.project)
684
+ )
685
+ );
686
+
687
+ if (oldColumnName && oldColumnName !== newColumnName) {
688
+ renames[oldColumnName] = newColumnName;
689
+ }
690
+ savedMappings[prop.id] = newColumnName;
691
+ });
692
+
693
+ try {
694
+ localStorage.setItem(mappingKey, JSON.stringify(savedMappings));
695
+ } catch (e) {
696
+ console.error(
697
+ 'Failed to save column mappings to localStorage:',
698
+ e
699
+ );
700
+ }
701
+
702
+ tablesToMigrate.push({
703
+ tableName: table.definition.name,
704
+ desiredColumnsMeta: table.definition.columns,
705
+ renames
706
+ });
707
+ });
708
+
709
+ const dbName = Database.toDbName(this.entity);
710
+
711
+ const existingDbDef = await describeExisting(dbName);
712
+
713
+ const sanitizedExistingDBDef = !!existingDbDef
714
+ ? {
715
+ ...existingDbDef,
716
+ tables:
717
+ existingDbDef?.tables.filter(
718
+ (table) => table.name !== 'JsStore_Meta'
719
+ ) || []
720
+ }
721
+ : null;
722
+
723
+ const existingVersion = sanitizedExistingDBDef?.version || 1;
724
+
725
+ const tablesToRecreate: {
726
+ oldTableName: string;
727
+ newTableName: string;
728
+ oldPrimaryKey: string;
729
+ newPrimaryKey: string;
730
+ rows: any[];
731
+ }[] = [];
732
+
733
+ let connection = this.connection || (await initLocalDBConnection());
734
+
735
+ if (sanitizedExistingDBDef) {
736
+ for (const table of tables) {
737
+ const existingTable = sanitizedExistingDBDef.tables.find(
738
+ (t) => t.name === table.definition.name
739
+ );
740
+ if (existingTable) {
741
+ const desiredPrimaryKeyCol = Object.entries(
742
+ table.definition.columns
743
+ ).find(([_, colMeta]) => colMeta.primaryKey);
744
+ const desiredPrimaryKeyName = desiredPrimaryKeyCol?.[0];
745
+
746
+ const existingPrimaryKeyCol = Object.entries(
747
+ existingTable.columns
748
+ ).find(([_, colMeta]) => colMeta.primaryKey);
749
+ const existingPrimaryKeyName = existingPrimaryKeyCol?.[0];
750
+
751
+ if (
752
+ existingPrimaryKeyName &&
753
+ desiredPrimaryKeyName &&
754
+ existingPrimaryKeyName !== desiredPrimaryKeyName
755
+ ) {
756
+ let rows: any[] = [];
757
+ try {
758
+ rows = await new Promise<any[]>(
759
+ (resolve, reject) => {
760
+ const request = indexedDB.open(dbName);
761
+ request.onsuccess = (event) => {
762
+ const db = (event.target as any).result;
763
+ try {
764
+ if (
765
+ !db.objectStoreNames.contains(
766
+ table.definition.name
767
+ )
768
+ ) {
769
+ db.close();
770
+ resolve([]);
771
+ return;
772
+ }
773
+ const transaction = db.transaction(
774
+ table.definition.name,
775
+ 'readonly'
776
+ );
777
+ const store =
778
+ transaction.objectStore(
779
+ table.definition.name
780
+ );
781
+ const getAllReq = store.getAll();
782
+ getAllReq.onsuccess = () => {
783
+ const result =
784
+ getAllReq.result || [];
785
+ db.close();
786
+ resolve(result);
787
+ };
788
+ getAllReq.onerror = () => {
789
+ db.close();
790
+ reject(getAllReq.error);
791
+ };
792
+ } catch (err) {
793
+ db.close();
794
+ reject(err);
795
+ }
796
+ };
797
+ request.onerror = (event) => {
798
+ reject((event.target as any).error);
799
+ };
800
+ }
801
+ );
802
+ } catch (e) {
803
+ console.error(
804
+ `Failed to fetch rows for recreating table ${table.definition.name}:`,
805
+ e
806
+ );
807
+ }
808
+
809
+ tablesToRecreate.push({
810
+ oldTableName: table.definition.name,
811
+ newTableName: table.definition.name,
812
+ oldPrimaryKey: existingPrimaryKeyName,
813
+ newPrimaryKey: desiredPrimaryKeyName,
814
+ rows
815
+ });
816
+ }
817
+ }
818
+ }
819
+ }
820
+
821
+ // The target definition is the one that we want to have
822
+ // We will populate it, from the difference between the existing definition and the target definition
823
+ const finalDef: IDataBase = {
824
+ name: dbName,
825
+ tables: [] as ITable[]
826
+ };
827
+
828
+ // If anything has changed, increment the version
829
+ let hasChanged: boolean = false;
830
+ let nextVersion = existingVersion;
831
+
832
+ const increaseVersion = () => {
833
+ if (!hasChanged) {
834
+ hasChanged = true;
835
+ nextVersion++;
836
+ }
837
+
838
+ return nextVersion;
839
+ };
840
+
841
+ const tablesToAdd: ITable[] = [];
842
+ const tablesToRemove: ITable[] = [];
843
+ const tablesToModify: ITable[] = [];
844
+
845
+ // Loop over the existing definition tables
846
+ sanitizedExistingDBDef?.tables?.forEach((table) => {
847
+ const desiredTable = tables.find(
848
+ (t) => t.definition.name === table.name
849
+ );
850
+
851
+ if (!desiredTable) {
852
+ increaseVersion();
853
+ tablesToRemove.push(table);
854
+ }
855
+ });
856
+
857
+ const columnsToDrop: { tableName: string; columnName: string }[] = [];
858
+
859
+ tables.forEach((table) => {
860
+ const existingTable = sanitizedExistingDBDef?.tables?.find(
861
+ (t) => t.name === table.definition.name
862
+ );
863
+ if (existingTable) {
864
+ Object.keys(existingTable.columns).forEach(
865
+ (existingColumnName) => {
866
+ if (!table.definition.columns[existingColumnName]) {
867
+ columnsToDrop.push({
868
+ tableName: table.definition.name,
869
+ columnName: existingColumnName
870
+ });
871
+ }
872
+ }
873
+ );
874
+ }
875
+ });
876
+
877
+ if (
878
+ tablesToRecreate.length > 0 ||
879
+ tablesToRemove.length > 0 ||
880
+ columnsToDrop.length > 0
881
+ ) {
882
+ const deleteVersion = existingVersion + 1;
883
+ nextVersion = deleteVersion + 1;
884
+ hasChanged = true;
885
+
886
+ // Natively delete the object stores and indexes
887
+ await new Promise<void>((resolveDelete) => {
888
+ if (connection) {
889
+ connection.terminate();
890
+ }
891
+ const request = indexedDB.open(dbName, deleteVersion);
892
+ request.onupgradeneeded = (event) => {
893
+ const db = request.result;
894
+ const transaction = request.transaction;
895
+
896
+ tablesToRecreate.forEach((t) => {
897
+ if (db.objectStoreNames.contains(t.oldTableName)) {
898
+ db.deleteObjectStore(t.oldTableName);
899
+ }
900
+ });
901
+ tablesToRemove.forEach((t) => {
902
+ if (db.objectStoreNames.contains(t.name)) {
903
+ db.deleteObjectStore(t.name);
904
+ }
905
+ });
906
+
907
+ if (transaction) {
908
+ columnsToDrop.forEach((c) => {
909
+ if (db.objectStoreNames.contains(c.tableName)) {
910
+ const store = transaction.objectStore(
911
+ c.tableName
912
+ );
913
+ if (store.indexNames.contains(c.columnName)) {
914
+ store.deleteIndex(c.columnName);
915
+ }
916
+ }
917
+ });
918
+ }
919
+ };
920
+ request.onsuccess = (event) => {
921
+ const db = request.result;
922
+ db.close();
923
+ resolveDelete();
924
+ };
925
+ request.onerror = () => {
926
+ resolveDelete();
927
+ };
928
+ request.onblocked = () => {
929
+ console.warn(
930
+ 'IndexedDB open blocked during table recreation'
931
+ );
932
+ resolveDelete();
933
+ };
934
+ });
935
+
936
+ // Re-initialize connection
937
+ connection = await initLocalDBConnection();
938
+ }
939
+
940
+ /*
941
+ Compare both definitions
942
+ Loop over the desired definition tables
943
+ For each table, check if all columns exist, if not, add the new column definition as 'alter -> add'
944
+ For each unrecognized column, add the column definition as 'alter -> drop'
945
+ For each colmn present in both, check if the data-type and other metadata matches, if not, add the new column definition as 'alter -> modify'
946
+ */
947
+
948
+ // When a data-type changes, we need to migrate the data
949
+ // So we store here a list of migrations needed as a from -> to pair of property version instances
950
+ // The from is the last PropertyState version that had a data-type like the one in the database
951
+ // The to is the new PropertyState version that is being added to the table
952
+ const migrationRequests: {
953
+ from: PropertyState;
954
+ to: PropertyState;
955
+ }[] = [];
956
+
957
+ // Loop over the desired definition tables
958
+ tables.forEach((table) => {
959
+ const existingTable = sanitizedExistingDBDef.tables.find(
960
+ (t) => t.name === table.definition.name
961
+ );
962
+
963
+ const isRecreating = tablesToRecreate.some(
964
+ (t) => t.oldTableName === table.definition.name
965
+ );
966
+
967
+ if (!existingTable || isRecreating) {
968
+ increaseVersion();
969
+ tablesToAdd.push(table.definition);
970
+ finalDef.tables.push(table.definition);
971
+ return;
972
+ }
973
+
974
+ let finalTable: ITable = {
975
+ name: table.definition.name,
976
+ columns: table.definition.columns
977
+ };
978
+
979
+ // Loop over the desired definition columns
980
+ Object.entries(table.definition.columns).forEach(
981
+ ([targetColumnName, targetColMeta]) => {
982
+ const columnEntries = Object.entries(existingTable.columns);
983
+
984
+ const result = columnEntries.find(
985
+ ([cName, cMeta]) => cName === targetColumnName
986
+ );
987
+
988
+ const existingColumnName = result?.[0];
989
+ const existingColMeta = result?.[1];
990
+
991
+ if (!existingColumnName) {
992
+ increaseVersion();
993
+ tablesToModify.push(table.definition);
994
+ finalTable = {
995
+ ...finalTable,
996
+ alter: {
997
+ ...(finalTable.alter || {}),
998
+ [nextVersion]: {
999
+ ...(finalTable.alter?.[nextVersion] || {}),
1000
+ add: {
1001
+ ...(finalTable.alter?.[nextVersion]
1002
+ ?.add || {}),
1003
+ [targetColumnName]: targetColMeta
1004
+ }
1005
+ }
1006
+ }
1007
+ };
1008
+ return;
1009
+ }
1010
+
1011
+ // Check if the data-type and other metadata matches
1012
+ if (existingColMeta.dataType !== targetColMeta.dataType) {
1013
+ increaseVersion();
1014
+ tablesToModify.push(table.definition);
1015
+ finalTable = {
1016
+ ...finalTable,
1017
+ alter: {
1018
+ ...(finalTable.alter || {}),
1019
+ [nextVersion]: {
1020
+ ...(finalTable.alter?.[nextVersion] || {}),
1021
+ modify: {
1022
+ ...(finalTable.alter?.[nextVersion]
1023
+ ?.modify || {}),
1024
+ [targetColumnName]: targetColMeta
1025
+ }
1026
+ }
1027
+ }
1028
+ };
1029
+
1030
+ // TODO: Here subscribe to 'migrationRequests'
1031
+
1032
+ return;
1033
+ }
1034
+ }
1035
+ );
1036
+
1037
+ // Loop over the existing definition columns
1038
+ Object.entries(existingTable.columns).forEach(
1039
+ ([existingColumnName, existingColMeta]) => {
1040
+ const targetColMeta =
1041
+ table.definition.columns[existingColumnName];
1042
+
1043
+ if (!targetColMeta) {
1044
+ increaseVersion();
1045
+ tablesToModify.push(table.definition);
1046
+ finalTable = {
1047
+ ...finalTable,
1048
+ alter: {
1049
+ ...(finalTable.alter || {}),
1050
+ [nextVersion]: {
1051
+ ...(finalTable.alter?.[nextVersion] || {}),
1052
+ drop: {
1053
+ ...(finalTable.alter?.[nextVersion]
1054
+ ?.drop || {}),
1055
+ [existingColumnName]: null
1056
+ }
1057
+ }
1058
+ }
1059
+ };
1060
+ return;
1061
+ }
1062
+ }
1063
+ );
1064
+
1065
+ finalDef.tables.push(finalTable);
1066
+ });
1067
+
1068
+ finalDef.version = nextVersion;
1069
+
1070
+ // If a database exists, it will not add the new tables, it will just initialize the database connection
1071
+ const created = await initLocalIndexedDatabase(connection, finalDef);
1072
+
1073
+ this.connection = connection;
1074
+
1075
+ // Perform IndexedDB schema and data migration and compaction
1076
+ await migrateAndCompactData(dbName, tablesToMigrate);
1077
+
1078
+ // Restore recreated tables data
1079
+ if (tablesToRecreate.length > 0) {
1080
+ for (const t of tablesToRecreate) {
1081
+ if (t.rows.length > 0) {
1082
+ const mappedRows = t.rows.map((row) => {
1083
+ const newRow = { ...row };
1084
+ if (row[t.oldPrimaryKey] !== undefined) {
1085
+ newRow[t.newPrimaryKey] = row[t.oldPrimaryKey];
1086
+ delete newRow[t.oldPrimaryKey];
1087
+ }
1088
+ return newRow;
1089
+ });
1090
+ try {
1091
+ await connection.insert({
1092
+ into: t.newTableName,
1093
+ values: mappedRows
1094
+ });
1095
+ } catch (e) {
1096
+ console.error(
1097
+ `Failed to restore rows for table ${t.newTableName}:`,
1098
+ e
1099
+ );
1100
+ }
1101
+ }
1102
+ }
1103
+ }
1104
+
1105
+ // Init tables
1106
+ await Promise.all(
1107
+ tables.map(async (table) => {
1108
+ const existingTableInstance = this.tables.find(
1109
+ (t) => t.tableName === table.definition.name
1110
+ );
1111
+ const tableInstance =
1112
+ existingTableInstance ||
1113
+ new Table(table.definition.name, table.entity, this);
1114
+
1115
+ await tableInstance.init();
1116
+
1117
+ this.tables.push(tableInstance);
1118
+ })
1119
+ );
1120
+
1121
+ this.store.project.emit('local-databases-updated', this);
1122
+
1123
+ return this;
1124
+ }
1125
+
1126
+ async disconnect(): Promise<void> {
1127
+ await this.connection.terminate();
1128
+ this.connection = null;
1129
+ }
1130
+
1131
+ async describe(initialQuery: any): Promise<IDataBaseDetails> {
1132
+ // @ts-ignore Because it is a protected property
1133
+ const databaseStructureDef = this.connection.database;
1134
+
1135
+ let preselectedTableDef: ITable | null = null;
1136
+ let preselectedTable: Table | null = null;
1137
+ let preselectedTableEntity: DefinitionEntityState | null = null;
1138
+
1139
+ if (initialQuery.tableName) {
1140
+ preselectedTableDef = databaseStructureDef.tables.find(
1141
+ (table) => table.name === initialQuery.tableName
1142
+ );
1143
+
1144
+ preselectedTable = this.tables.find(
1145
+ (t) => t.tableName === initialQuery.tableName
1146
+ );
1147
+
1148
+ preselectedTableEntity = preselectedTable?.entity;
1149
+ }
1150
+
1151
+ if (!preselectedTableDef) {
1152
+ return {
1153
+ items: databaseStructureDef.tables.map((t) => {
1154
+ const table = this.tables.find(
1155
+ (ta) => ta.tableName === t.name
1156
+ );
1157
+
1158
+ return table;
1159
+ }),
1160
+ data: null,
1161
+ total: this.total
1162
+ };
1163
+ }
1164
+
1165
+ const rows: {
1166
+ [columnName: string]: any;
1167
+ }[] = initialQuery.tableName
1168
+ ? await this.connection.select({
1169
+ from: initialQuery.tableName,
1170
+ limit: initialQuery.limit
1171
+ })
1172
+ : [];
1173
+
1174
+ const initialColumns: searchStatementUtils.IColumnMapping[] = (
1175
+ preselectedTable?.columns || []
1176
+ ).map((column) => ({
1177
+ column: column,
1178
+ as: column.columnName
1179
+ }));
1180
+
1181
+ return {
1182
+ items: databaseStructureDef.tables.map((tabl) => {
1183
+ const table = this.tables.find(
1184
+ (t) => t.tableName === tabl.name
1185
+ );
1186
+
1187
+ return table;
1188
+ }),
1189
+ data: initialQuery.tableName
1190
+ ? {
1191
+ tableName: preselectedTableDef.name,
1192
+ entity: preselectedTableEntity,
1193
+ columns: initialColumns,
1194
+ rows: rows,
1195
+ total: preselectedTable.total
1196
+ }
1197
+ : null,
1198
+ total: this.total
1199
+ };
1200
+ }
1201
+
1202
+ // Given an entity id that represents a table in the database
1203
+ // Return the table object
1204
+ getTable(entityIdOrTableName: string): Table {
1205
+ return this.tables.find(
1206
+ (table) =>
1207
+ table.entity.id === entityIdOrTableName ||
1208
+ table.tableName === entityIdOrTableName
1209
+ );
1210
+ }
1211
+
1212
+ async checkForeignKeyConstraintsForUpdate(
1213
+ tableName: string,
1214
+ setValues: any
1215
+ ) {
1216
+ const table = this.getTable(tableName);
1217
+ if (!table) return;
1218
+
1219
+ for (const column of table.columns) {
1220
+ const foreignKeyValue = setValues[column.columnName];
1221
+ if (foreignKeyValue === undefined) {
1222
+ continue; // Not being updated
1223
+ }
1224
+
1225
+ const dataType = column.property?.dataType;
1226
+ const fkInfo = resolveForeignKeyInfo(dataType);
1227
+ if (fkInfo) {
1228
+ const referencedEntity = fkInfo.entity;
1229
+ const referencedDb =
1230
+ this.store.getDatabaseForPersistedEntity(referencedEntity);
1231
+ if (!referencedDb) continue;
1232
+
1233
+ const referencedTableName = toPascalCase(
1234
+ resolveEntityName(
1235
+ referencedEntity,
1236
+ this.store.project.logic
1237
+ )
1238
+ );
1239
+ const referencedTable =
1240
+ referencedDb.getTable(referencedTableName);
1241
+ if (!referencedTable) continue;
1242
+
1243
+ if (foreignKeyValue == null) {
1244
+ continue;
1245
+ }
1246
+
1247
+ // Find the primary key name of the referenced table
1248
+ const referencedPrimaryKeyName = fkInfo.foreignKey.key;
1249
+
1250
+ // Query the referenced table
1251
+ const referencedRows = await referencedDb.connection.select({
1252
+ from: referencedTableName,
1253
+ where: {
1254
+ [referencedPrimaryKeyName]: foreignKeyValue
1255
+ },
1256
+ limit: 1
1257
+ });
1258
+
1259
+ if (referencedRows.length === 0) {
1260
+ throw new Error(
1261
+ `SequelizeDatabaseError: Foreign key constraint failed: Referenced record in table "${referencedTableName}" does not exist`
1262
+ );
1263
+ }
1264
+ }
1265
+ }
1266
+ }
1267
+
1268
+ async checkForeignKeyConstraintsForInsert(
1269
+ tableName: string,
1270
+ values: any[]
1271
+ ) {
1272
+ for (const row of values) {
1273
+ await this.checkForeignKeyConstraintsForUpdate(tableName, row);
1274
+ }
1275
+ }
1276
+
1277
+ async insert(query: IInsertQuery): Promise<any> {
1278
+ const table = this.getTable(query.into);
1279
+ if (table && query.values) {
1280
+ query.values = query.values.map((row) => {
1281
+ const sanitizedRow: any = { ...row };
1282
+ table.columns.forEach((column) => {
1283
+ const value = row[column.columnName];
1284
+ if (value !== undefined) {
1285
+ sanitizedRow[column.columnName] =
1286
+ sanitizeValueForColumn(value, column);
1287
+ }
1288
+ });
1289
+ return sanitizedRow;
1290
+ });
1291
+
1292
+ await this.checkForeignKeyConstraintsForInsert(
1293
+ query.into,
1294
+ query.values
1295
+ );
1296
+ }
1297
+
1298
+ const result = await this.connection.insert(query);
1299
+
1300
+ // Update the total number of rows in the table
1301
+ if (table) {
1302
+ table.total += query.values.length;
1303
+ }
1304
+
1305
+ this.store.project.emit('local-databases-updated', this);
1306
+
1307
+ return result;
1308
+ }
1309
+
1310
+ async update(query: IUpdateQuery): Promise<any> {
1311
+ const table = this.getTable(query.in);
1312
+ if (table && query.set) {
1313
+ const sanitizedSet: any = { ...query.set };
1314
+ table.columns.forEach((column) => {
1315
+ const value = query.set[column.columnName];
1316
+ if (value !== undefined) {
1317
+ sanitizedSet[column.columnName] = sanitizeValueForColumn(
1318
+ value,
1319
+ column
1320
+ );
1321
+ }
1322
+ });
1323
+ query.set = sanitizedSet;
1324
+
1325
+ await this.checkForeignKeyConstraintsForUpdate(query.in, query.set);
1326
+ }
1327
+
1328
+ const result = await this.connection.update(query);
1329
+
1330
+ this.store.project.emit('local-databases-updated', this);
1331
+
1332
+ return result;
1333
+ }
1334
+
1335
+ async remove(query: IRemoveQuery): Promise<any> {
1336
+ // Find rows being targeted for deletion
1337
+ const rowsToDelete = await this.connection.select({
1338
+ from: query.from,
1339
+ where: query.where
1340
+ });
1341
+
1342
+ if (rowsToDelete.length > 0) {
1343
+ const table = this.getTable(query.from);
1344
+ const primaryKeyName =
1345
+ table.columns.find((col) => col.primaryKey)?.columnName || 'id';
1346
+ const primaryKeyValues = rowsToDelete
1347
+ .map((row) => (row as any)[primaryKeyName])
1348
+ .filter((val) => val != null);
1349
+
1350
+ if (primaryKeyValues.length > 0) {
1351
+ // Iterate over all tables in this database
1352
+ for (const otherTable of this.tables) {
1353
+ for (const column of otherTable.columns) {
1354
+ const dataType = column.property?.dataType;
1355
+ if (
1356
+ dataType?.entity?.type ===
1357
+ EntityType.DefinitionEntity &&
1358
+ dataType.entity.id === table.entity.id
1359
+ ) {
1360
+ // This column references table.entity!
1361
+ // Check if there are any rows in otherTable where this column holds one of primaryKeyValues
1362
+ const referencingRows =
1363
+ await this.connection.select({
1364
+ from: otherTable.tableName,
1365
+ where: {
1366
+ [column.columnName]: {
1367
+ in: primaryKeyValues
1368
+ }
1369
+ }
1370
+ });
1371
+
1372
+ const otherTablePrimaryKeyName =
1373
+ otherTable.columns.find((col) => col.primaryKey)
1374
+ ?.columnName || 'id';
1375
+ const actualReferencingRows =
1376
+ referencingRows.filter((row) => {
1377
+ const rowId = (row as any)[
1378
+ otherTablePrimaryKeyName
1379
+ ];
1380
+ // If this referencing row is also among the ones being deleted, it's fine!
1381
+ if (otherTable.tableName === query.from) {
1382
+ return !primaryKeyValues.includes(
1383
+ rowId
1384
+ );
1385
+ }
1386
+ return true;
1387
+ });
1388
+
1389
+ if (actualReferencingRows.length > 0) {
1390
+ throw new Error(
1391
+ `SequelizeForeignKeyConstraintError: update or delete on table "${query.from}" violates foreign key constraint on table "${otherTable.tableName}"`
1392
+ );
1393
+ }
1394
+ }
1395
+ }
1396
+ }
1397
+ }
1398
+ }
1399
+
1400
+ await this.connection.remove(query);
1401
+
1402
+ // Recalculate the total number of rows in the table
1403
+ const table = this.getTable(query.from);
1404
+
1405
+ await table.updateTotal();
1406
+
1407
+ this.store.project.emit('local-databases-updated', this);
1408
+ }
1409
+
1410
+ async clear(table: string): Promise<any> {
1411
+ await this.connection.clear(table);
1412
+
1413
+ // Recalculate the total number of rows in the table
1414
+ const tableInstance = this.getTable(table);
1415
+
1416
+ await tableInstance.updateTotal();
1417
+
1418
+ this.store.project.emit('local-databases-updated', this);
1419
+ }
1420
+
1421
+ select(query: ISelectQuery): Promise<
1422
+ {
1423
+ [columnName: string]: any;
1424
+ }[]
1425
+ > {
1426
+ return this.connection.select(query);
1427
+ }
1428
+
1429
+ /*
1430
+ Jsstore doesn't have feature parity with SQL
1431
+ So we process the 'where' clause separately
1432
+ This means, the first query is executed in jsstore without any 'where' clause filters
1433
+ Then, we filter the results in memory
1434
+
1435
+ To achieve the filtering of the results according to the potentially complex and nested 'where' clause
1436
+ We divide the 'where' clause into smaller parts, each representing a single 'where' condition
1437
+ Then merge the results base on "and" and "or" conditions, where if a row passes all "and" conditions, it is included
1438
+ And if a row passes any "or" condition, it is included
1439
+ */
1440
+ async processFiltering(
1441
+ selectStatement: SelectStmt,
1442
+ rows: {
1443
+ [columnName: string]: any;
1444
+ }[]
1445
+ ): Promise<
1446
+ {
1447
+ [columnName: string]: any;
1448
+ }[]
1449
+ > {
1450
+ const whereClause =
1451
+ searchStatementUtils.getWhereClauseFromSelectStatement(
1452
+ selectStatement
1453
+ );
1454
+
1455
+ if (!whereClause) {
1456
+ return rows;
1457
+ }
1458
+
1459
+ return rows;
1460
+ }
1461
+
1462
+ evaluateNode(
1463
+ node: SearchNode,
1464
+ dataSources: { source: DataSource; data: any }[] = []
1465
+ ): any {
1466
+ if (!node) return null;
1467
+
1468
+ if (node.type === SearchStatementNodeType.LiteralValue) {
1469
+ return (node as any).value;
1470
+ }
1471
+
1472
+ if (node.type === SearchStatementNodeType.ColumnRef) {
1473
+ const col = node as ColumnRef;
1474
+ const ds = dataSources.find((d) => d.source?.as === col.parent?.as);
1475
+ if (ds && ds.data) {
1476
+ let ogColumnName = '';
1477
+ if (col.source?.type === EntityType.Property) {
1478
+ ogColumnName =
1479
+ (col.source as PropertyState)?.codeName ||
1480
+ lowercaseFirstLetter(
1481
+ toCamelCase(
1482
+ resolveEntityName(
1483
+ col.source as PropertyState,
1484
+ (col.source as PropertyState).project
1485
+ )
1486
+ )
1487
+ );
1488
+ } else if (
1489
+ col.source?.type === SearchStatementNodeType.LiteralValue
1490
+ ) {
1491
+ ogColumnName = (col.source as any).value?.toString() || '';
1492
+ } else if (
1493
+ col.source?.type === SearchStatementNodeType.ColumnRef
1494
+ ) {
1495
+ ogColumnName = (col.source as ColumnRef).as;
1496
+ } else {
1497
+ ogColumnName = col.as;
1498
+ }
1499
+ return ds.data[ogColumnName];
1500
+ }
1501
+ return null;
1502
+ }
1503
+
1504
+ if (node.type === SearchStatementNodeType.FunctionCall) {
1505
+ const func = node as FunctionCall;
1506
+ const args = func.arguments.map((arg) =>
1507
+ this.evaluateNode(arg as any, dataSources)
1508
+ );
1509
+ const funcName = func.functionName?.toLowerCase();
1510
+
1511
+ switch (funcName) {
1512
+ case 'upper':
1513
+ return typeof args[0] === 'string'
1514
+ ? args[0].toUpperCase()
1515
+ : args[0];
1516
+ case 'lower':
1517
+ return typeof args[0] === 'string'
1518
+ ? args[0].toLowerCase()
1519
+ : args[0];
1520
+ case 'length':
1521
+ return typeof args[0] === 'string' ? args[0].length : 0;
1522
+ case 'concat':
1523
+ return args.map((a) => a ?? '').join('');
1524
+ case 'coalesce':
1525
+ return (
1526
+ args.find((a) => a !== null && a !== undefined) ?? null
1527
+ );
1528
+ case 'round': {
1529
+ const num = Number(args[0]);
1530
+ const decimals =
1531
+ args[1] !== undefined ? Number(args[1]) : 0;
1532
+ if (isNaN(num)) return null;
1533
+ const factor = Math.pow(10, decimals);
1534
+ return Math.round(num * factor) / factor;
1535
+ }
1536
+ case 'now':
1537
+ return new Date();
1538
+ case 'json_build_object': {
1539
+ const obj: any = {};
1540
+ for (let i = 0; i < args.length; i += 2) {
1541
+ if (args[i] !== undefined && args[i] !== null) {
1542
+ obj[String(args[i])] = args[i + 1];
1543
+ }
1544
+ }
1545
+ return obj;
1546
+ }
1547
+ case 'row_to_json':
1548
+ return args[0];
1549
+ default:
1550
+ return null;
1551
+ }
1552
+ }
1553
+ return null;
1554
+ }
1555
+
1556
+ evaluateAggregateNode(
1557
+ func: FunctionCall,
1558
+ rows: any[],
1559
+ statement: SearchStatementState
1560
+ ): any {
1561
+ const funcName = func.functionName?.toLowerCase();
1562
+ const args = func.arguments;
1563
+
1564
+ if (funcName === 'count') {
1565
+ if (args[0]?.type === SearchStatementNodeType.AllColumnsSelector)
1566
+ return rows.length;
1567
+ let count = 0;
1568
+ rows.forEach((row) => {
1569
+ const dataSources = [
1570
+ { source: statement.from, data: row },
1571
+ ...statement.aggregations.map((a) => ({
1572
+ source: a.dataSource,
1573
+ data: row
1574
+ }))
1575
+ ];
1576
+ const val = this.evaluateNode(
1577
+ args[0] as any,
1578
+ dataSources as any
1579
+ );
1580
+ if (val !== null && val !== undefined) count++;
1581
+ });
1582
+ return count;
1583
+ }
1584
+
1585
+ if (['sum', 'max', 'min', 'json_agg'].includes(funcName)) {
1586
+ let sum = 0,
1587
+ max: number | null = null,
1588
+ min: number | null = null;
1589
+ const arr: any[] = [];
1590
+
1591
+ rows.forEach((row) => {
1592
+ const dataSources = [
1593
+ { source: statement.from, data: row },
1594
+ ...statement.aggregations.map((a) => ({
1595
+ source: a.dataSource,
1596
+ data: row
1597
+ }))
1598
+ ];
1599
+ const val = this.evaluateNode(
1600
+ args[0] as any,
1601
+ dataSources as any
1602
+ );
1603
+
1604
+ if (
1605
+ funcName === 'sum' &&
1606
+ typeof val === 'number' &&
1607
+ !isNaN(val)
1608
+ )
1609
+ sum += val;
1610
+ if (funcName === 'max' && val !== null && val !== undefined)
1611
+ max = max === null || val > max ? val : max;
1612
+ if (funcName === 'min' && val !== null && val !== undefined)
1613
+ min = min === null || val < min ? val : min;
1614
+ if (funcName === 'json_agg') arr.push(val);
1615
+ });
1616
+
1617
+ if (funcName === 'sum') return sum;
1618
+ if (funcName === 'max') return max;
1619
+ if (funcName === 'min') return min;
1620
+ if (funcName === 'json_agg') return arr;
1621
+ }
1622
+ return null;
1623
+ }
1624
+
1625
+ checkCondition(
1626
+ condition: WhereStatement,
1627
+ dataSources: {
1628
+ source: DataSource;
1629
+ data: {
1630
+ [columnName: string]: RawResults;
1631
+ };
1632
+ }[]
1633
+ ): boolean {
1634
+ if (!condition?.isValid) {
1635
+ return true;
1636
+ }
1637
+
1638
+ if (condition.operator && condition.left && condition.right) {
1639
+ let leftValue = this.evaluateNode(condition.left, dataSources);
1640
+ let rightValue = this.evaluateNode(condition.right, dataSources);
1641
+
1642
+ if (leftValue === undefined) {
1643
+ leftValue = null;
1644
+ }
1645
+ if (rightValue === undefined) {
1646
+ rightValue = null;
1647
+ }
1648
+
1649
+ switch (condition.operator) {
1650
+ case WhereStatementOperator.Equal: {
1651
+ return areValuesEqual(leftValue, rightValue);
1652
+ }
1653
+ case WhereStatementOperator.NotEqual: {
1654
+ return !areValuesEqual(leftValue, rightValue);
1655
+ }
1656
+ case WhereStatementOperator.Like: {
1657
+ if (leftValue === null || rightValue === null) {
1658
+ return false;
1659
+ }
1660
+ const regex = new RegExp(
1661
+ `^${rightValue.toString().replace(/%/g, '.*').replace(/_/g, '.')}$`
1662
+ );
1663
+ return regex.test(leftValue.toString());
1664
+ }
1665
+ case WhereStatementOperator.NotLike: {
1666
+ if (leftValue === null || rightValue === null) {
1667
+ return false;
1668
+ }
1669
+ const regex = new RegExp(
1670
+ `^${rightValue.toString().replace(/%/g, '.*').replace(/_/g, '.')}$`
1671
+ );
1672
+ return !regex.test(leftValue.toString());
1673
+ }
1674
+ case WhereStatementOperator.In: {
1675
+ if (!Array.isArray(rightValue))
1676
+ throw new Error(
1677
+ 'Right value of "IN" operator must be an array'
1678
+ );
1679
+ return rightValue.some((val) =>
1680
+ areValuesEqual(leftValue, val)
1681
+ );
1682
+ }
1683
+ case WhereStatementOperator.Between: {
1684
+ if (!Array.isArray(rightValue) || rightValue.length !== 2)
1685
+ throw new Error(
1686
+ 'Right value of "BETWEEN" operator must be an array of two elements'
1687
+ );
1688
+ if (
1689
+ leftValue === null ||
1690
+ rightValue[0] === null ||
1691
+ rightValue[1] === null
1692
+ ) {
1693
+ return false;
1694
+ }
1695
+ const [l, r0] = coerceToComparable(
1696
+ leftValue,
1697
+ rightValue[0]
1698
+ );
1699
+ const [, r1] = coerceToComparable(l, rightValue[1]);
1700
+ return l >= r0 && l <= r1;
1701
+ }
1702
+ case WhereStatementOperator.BiggerThan: {
1703
+ if (leftValue === null || rightValue === null) {
1704
+ return false;
1705
+ }
1706
+ const [l, r] = coerceToComparable(leftValue, rightValue);
1707
+ return l > r;
1708
+ }
1709
+ case WhereStatementOperator.SmallerThan: {
1710
+ if (leftValue === null || rightValue === null) {
1711
+ return false;
1712
+ }
1713
+ const [l, r] = coerceToComparable(leftValue, rightValue);
1714
+ return l < r;
1715
+ }
1716
+ case WhereStatementOperator.BiggerThanOrEqualTo: {
1717
+ if (leftValue === null || rightValue === null) {
1718
+ return false;
1719
+ }
1720
+ const [l, r] = coerceToComparable(leftValue, rightValue);
1721
+ return l >= r;
1722
+ }
1723
+ case WhereStatementOperator.SmallerThanOrEqualTo: {
1724
+ if (leftValue === null || rightValue === null) {
1725
+ return false;
1726
+ }
1727
+ const [l, r] = coerceToComparable(leftValue, rightValue);
1728
+ return l <= r;
1729
+ }
1730
+ }
1731
+ }
1732
+
1733
+ let andGroupMatches = true;
1734
+
1735
+ if (!!condition.and.length) {
1736
+ andGroupMatches = condition.and.every((andCondition) =>
1737
+ this.checkCondition(andCondition, dataSources)
1738
+ );
1739
+ }
1740
+
1741
+ let orGroupMatches = true;
1742
+
1743
+ if (!!condition.or.length) {
1744
+ orGroupMatches = condition.or.some((orCondition) =>
1745
+ this.checkCondition(orCondition, dataSources)
1746
+ );
1747
+ }
1748
+
1749
+ return andGroupMatches && orGroupMatches;
1750
+ }
1751
+
1752
+ async applyJoinResults(
1753
+ statement: SearchStatementState,
1754
+ mainResults: {
1755
+ [columnName: string]: RawResults;
1756
+ }[],
1757
+ joins: {
1758
+ aggregation: AggregationStatement;
1759
+ results: {
1760
+ [columnName: string]: RawResults;
1761
+ }[];
1762
+ }[]
1763
+ ): Promise<
1764
+ {
1765
+ [columnName: string]: RawResults;
1766
+ }[]
1767
+ > {
1768
+ let joinedResults = mainResults;
1769
+
1770
+ joinedResults = joins.reduce((acc, join) => {
1771
+ const newAcc = [...acc];
1772
+
1773
+ if (join.aggregation.joinType === AggregationStatementType.Inner) {
1774
+ // Inner join
1775
+ // Loop over each row in the main results
1776
+ // And join the results from the join results
1777
+ // If the join condition is met
1778
+ newAcc.forEach((mainRow) => {
1779
+ join.results.forEach((joinRow) => {
1780
+ // Check if the join condition is met
1781
+ // If it is, merge the results
1782
+ // If not, ignore the join result
1783
+ if (
1784
+ this.checkCondition(join.aggregation.on, [
1785
+ {
1786
+ source: statement.from,
1787
+ data: mainRow
1788
+ },
1789
+ {
1790
+ source: join.aggregation.dataSource,
1791
+ data: joinRow
1792
+ }
1793
+ ])
1794
+ ) {
1795
+ newAcc.push({ ...mainRow, ...joinRow });
1796
+ }
1797
+ });
1798
+ });
1799
+ } else if (
1800
+ join.aggregation.joinType === AggregationStatementType.Left
1801
+ ) {
1802
+ // Left join
1803
+ // Loop over each row in the main results
1804
+ // And join the results from the join results
1805
+ // If the join condition is met
1806
+ newAcc.forEach((mainRow) => {
1807
+ let matchFound = false;
1808
+
1809
+ join.results.forEach((joinRow) => {
1810
+ // Check if the join condition is met
1811
+ // If it is, merge the results
1812
+ // If not, ignore the join result
1813
+ if (
1814
+ this.checkCondition(join.aggregation.on, [
1815
+ {
1816
+ source: statement.from,
1817
+ data: mainRow
1818
+ },
1819
+ {
1820
+ source: join.aggregation.dataSource,
1821
+ data: joinRow
1822
+ }
1823
+ ])
1824
+ ) {
1825
+ matchFound = true;
1826
+ newAcc.push({ ...mainRow, ...joinRow });
1827
+ }
1828
+ });
1829
+
1830
+ if (!matchFound) {
1831
+ const joinColumns = Object.keys(
1832
+ join.results[0] || {}
1833
+ ).reduce(
1834
+ (a, key) => {
1835
+ a[key] = null;
1836
+ return a;
1837
+ },
1838
+ {} as {
1839
+ [columnName: string]:
1840
+ | number
1841
+ | string
1842
+ | boolean
1843
+ | null
1844
+ | Date;
1845
+ }
1846
+ );
1847
+
1848
+ newAcc.push({ ...mainRow, ...joinColumns });
1849
+ }
1850
+ });
1851
+ } else if (
1852
+ join.aggregation.joinType === AggregationStatementType.Right
1853
+ ) {
1854
+ // Right join
1855
+ // Loop over each row in the main results
1856
+ // And join the results from the join results
1857
+ // If the join condition is met
1858
+ join.results.forEach((joinRow) => {
1859
+ let matchFound = false;
1860
+ // Check if the join condition is met
1861
+ // If it is, merge the results
1862
+ // If not, ignore the join result
1863
+ newAcc.forEach((mainRow) => {
1864
+ if (
1865
+ this.checkCondition(join.aggregation.on, [
1866
+ {
1867
+ source: statement.from,
1868
+ data: mainRow
1869
+ },
1870
+ {
1871
+ source: join.aggregation.dataSource,
1872
+ data: joinRow
1873
+ }
1874
+ ])
1875
+ ) {
1876
+ matchFound = true;
1877
+ newAcc.push({ ...mainRow, ...joinRow });
1878
+ }
1879
+ });
1880
+
1881
+ if (!matchFound) {
1882
+ const mainColumns = Object.keys(newAcc[0] || {}).reduce(
1883
+ (a, key) => {
1884
+ a[key] = null;
1885
+ return a;
1886
+ },
1887
+ {} as {
1888
+ [columnName: string]:
1889
+ | number
1890
+ | string
1891
+ | boolean
1892
+ | null
1893
+ | Date;
1894
+ }
1895
+ );
1896
+
1897
+ newAcc.push({ ...mainColumns, ...joinRow });
1898
+ }
1899
+ });
1900
+ } else if (
1901
+ join.aggregation.joinType === AggregationStatementType.Full
1902
+ ) {
1903
+ // Full join
1904
+ // Loop over each row in the main results
1905
+ // And join the results from the join results
1906
+ // If the join condition is met
1907
+ const matchedRows = new Set();
1908
+
1909
+ newAcc.forEach((mainRow) => {
1910
+ let matchFound = false;
1911
+
1912
+ join.results.forEach((joinRow) => {
1913
+ if (
1914
+ this.checkCondition(join.aggregation.on, [
1915
+ {
1916
+ source: statement.from,
1917
+ data: mainRow
1918
+ },
1919
+ {
1920
+ source: join.aggregation.dataSource,
1921
+ data: joinRow
1922
+ }
1923
+ ])
1924
+ ) {
1925
+ matchFound = true;
1926
+ newAcc.push({ ...mainRow, ...joinRow });
1927
+ matchedRows.add(joinRow);
1928
+ }
1929
+ });
1930
+
1931
+ if (!matchFound) {
1932
+ const joinColumns = Object.keys(
1933
+ join.results[0] || {}
1934
+ ).reduce(
1935
+ (a, key) => {
1936
+ a[key] = null;
1937
+ return a;
1938
+ },
1939
+ {} as {
1940
+ [columnName: string]:
1941
+ | number
1942
+ | string
1943
+ | boolean
1944
+ | null
1945
+ | Date;
1946
+ }
1947
+ );
1948
+
1949
+ newAcc.push({ ...mainRow, ...joinColumns });
1950
+ }
1951
+ });
1952
+
1953
+ join.results.forEach((joinRow) => {
1954
+ if (!matchedRows.has(joinRow)) {
1955
+ const mainColumns = Object.keys(newAcc[0] || {}).reduce(
1956
+ (a, key) => {
1957
+ a[key] = null;
1958
+ return a;
1959
+ },
1960
+ {} as {
1961
+ [columnName: string]:
1962
+ | number
1963
+ | string
1964
+ | boolean
1965
+ | null
1966
+ | Date;
1967
+ }
1968
+ );
1969
+
1970
+ newAcc.push({ ...mainColumns, ...joinRow });
1971
+ }
1972
+ });
1973
+ } else if (
1974
+ join.aggregation.joinType === AggregationStatementType.Cross
1975
+ ) {
1976
+ // Cross join
1977
+ // Loop over each row in the main results
1978
+ // And join the results from the join results
1979
+ // If the join condition is met
1980
+ newAcc.forEach((mainRow) => {
1981
+ join.results.forEach((joinRow) => {
1982
+ // Check if the join condition is met
1983
+ // If it is, merge the results
1984
+ // If not, ignore the join result
1985
+ newAcc.push({ ...mainRow, ...joinRow });
1986
+ });
1987
+ });
1988
+ } else if (
1989
+ join.aggregation.joinType === AggregationStatementType.Self
1990
+ ) {
1991
+ // Self join
1992
+ // Loop over each row in the main results
1993
+ // And join the results from the join results
1994
+ // If the join condition is met
1995
+ newAcc.forEach((mainRow) => {
1996
+ join.results.forEach((joinRow) => {
1997
+ // Check if the join condition is met
1998
+ // If it is, merge the results
1999
+ // If not, ignore the join result
2000
+ if (
2001
+ this.checkCondition(join.aggregation.on, [
2002
+ {
2003
+ source: statement.from,
2004
+ data: mainRow
2005
+ },
2006
+ {
2007
+ source: join.aggregation.dataSource,
2008
+ data: joinRow
2009
+ }
2010
+ ])
2011
+ ) {
2012
+ newAcc.push({ ...mainRow, ...joinRow });
2013
+ }
2014
+ });
2015
+ });
2016
+ } else if (
2017
+ join.aggregation.joinType === AggregationStatementType.Natural
2018
+ ) {
2019
+ // Get the keys that are common to both mainArray and joinArray rows
2020
+ const commonKeys =
2021
+ newAcc.length && join.results.length
2022
+ ? Object.keys(newAcc[0]).filter(
2023
+ (key) => key in join.results[0]
2024
+ )
2025
+ : [];
2026
+
2027
+ // Check if two rows have matching values for all common keys
2028
+ function hasMatchingCommonKeys(
2029
+ row1: {
2030
+ [columnName: string]: RawResults;
2031
+ },
2032
+ row2: {
2033
+ [columnName: string]: RawResults;
2034
+ }
2035
+ ) {
2036
+ return commonKeys.every((key) => row1[key] === row2[key]);
2037
+ }
2038
+
2039
+ // Natural join
2040
+ // Loop over each row in the main results
2041
+ // And join the results from the join results
2042
+ // If the join condition is met
2043
+ newAcc.forEach((mainRow) => {
2044
+ join.results.forEach((joinRow) => {
2045
+ // Check if the join condition is met
2046
+ // If it is, merge the results
2047
+ // If not, ignore the join result
2048
+ if (hasMatchingCommonKeys(mainRow, joinRow)) {
2049
+ newAcc.push({ ...mainRow, ...joinRow });
2050
+ }
2051
+ });
2052
+ });
2053
+ }
2054
+
2055
+ return newAcc;
2056
+ }, joinedResults);
2057
+
2058
+ return joinedResults;
2059
+ }
2060
+
2061
+ async applyWhereToResults(
2062
+ statement: SearchStatementState,
2063
+ results: {
2064
+ [columnName: string]: RawResults;
2065
+ }[]
2066
+ ): Promise<
2067
+ {
2068
+ [columnName: string]: RawResults;
2069
+ }[]
2070
+ > {
2071
+ const whereClause = statement.where;
2072
+
2073
+ if (!whereClause) {
2074
+ return results;
2075
+ }
2076
+
2077
+ const filteredResults = results.filter((result) => {
2078
+ const allSources = [
2079
+ {
2080
+ source: statement.from,
2081
+ data: result
2082
+ },
2083
+ ...statement.aggregations.map((aggregation) => ({
2084
+ source: aggregation.dataSource,
2085
+ data: result
2086
+ }))
2087
+ ];
2088
+
2089
+ return this.checkCondition(whereClause, allSources);
2090
+ });
2091
+
2092
+ return filteredResults;
2093
+ }
2094
+
2095
+ mapResults(
2096
+ statement: SearchStatementState,
2097
+ rows: { [columnName: string]: RawResults }[]
2098
+ ): { [columnName: string]: RawResults }[] {
2099
+ // 1. Check for aggregates (Giant Bucket Rule)
2100
+ const hasAggregates = statement.validSelections.some(
2101
+ (selection) =>
2102
+ selection.type === SearchStatementNodeType.FunctionCall &&
2103
+ (selection as FunctionCall).hasFunctionOfCategory(
2104
+ SQLFunctionCategory.Aggregate
2105
+ )
2106
+ );
2107
+
2108
+ if (hasAggregates) {
2109
+ const mappedResult: any = {};
2110
+ statement.validSelections.forEach((selection) => {
2111
+ // 1. Guard against AllColumnsSelector (it doesn't have an .as property)
2112
+ if (
2113
+ selection.type ===
2114
+ SearchStatementNodeType.AllColumnsSelector
2115
+ )
2116
+ return;
2117
+
2118
+ if (selection.type === SearchStatementNodeType.FunctionCall) {
2119
+ // 2. Cast to FunctionCall
2120
+ mappedResult[(selection as FunctionCall).as] =
2121
+ this.evaluateAggregateNode(
2122
+ selection as FunctionCall,
2123
+ rows,
2124
+ statement
2125
+ );
2126
+ } else {
2127
+ // 3. Cast to ColumnRef
2128
+ mappedResult[(selection as ColumnRef).as] =
2129
+ rows.length > 0
2130
+ ? this.evaluateNode(selection, [
2131
+ { source: statement.from, data: rows[0] }
2132
+ ])
2133
+ : null;
2134
+ }
2135
+ });
2136
+ return [mappedResult]; // Return exactly 1 row containing the aggregations
2137
+ }
2138
+
2139
+ // 2. Standard Scalar Mapping
2140
+ const mappedResults = rows.map((result) => {
2141
+ const mappedResult: any = {};
2142
+ const dataSources = [
2143
+ { source: statement.from, data: result },
2144
+ ...statement.aggregations.map((a) => ({
2145
+ source: a.dataSource,
2146
+ data: result
2147
+ }))
2148
+ ];
2149
+
2150
+ const selectAll = statement.validSelections.find(
2151
+ (selection) =>
2152
+ selection.type ===
2153
+ SearchStatementNodeType.AllColumnsSelector
2154
+ );
2155
+
2156
+ if (selectAll) {
2157
+ statement.columns.forEach((columnRef) => {
2158
+ mappedResult[columnRef.as] = this.evaluateNode(
2159
+ columnRef,
2160
+ dataSources
2161
+ );
2162
+ });
2163
+ return mappedResult;
2164
+ }
2165
+
2166
+ statement.validSelections.forEach((selection) => {
2167
+ if (!selection.isValid) return;
2168
+
2169
+ // 1. Tell TypeScript to skip AllColumnsSelector here too
2170
+ if (
2171
+ selection.type ===
2172
+ SearchStatementNodeType.AllColumnsSelector
2173
+ )
2174
+ return;
2175
+
2176
+ // 2. Safely cast to access the .as property
2177
+ const selectionWithAlias = selection as
2178
+ | ColumnRef
2179
+ | FunctionCall;
2180
+
2181
+ // Evaluate the node (Scalar Function or ColumnRef) directly from the row context!
2182
+ mappedResult[selectionWithAlias.as] = this.evaluateNode(
2183
+ selection,
2184
+ dataSources
2185
+ );
2186
+ });
2187
+
2188
+ return mappedResult;
2189
+ });
2190
+
2191
+ return mappedResults;
2192
+ }
2193
+
2194
+ selectionsToColumns(statement: SearchStatementState): ColumnRef[] {
2195
+ const selectAll = statement.validSelections.find(
2196
+ (selection) =>
2197
+ selection.type === SearchStatementNodeType.AllColumnsSelector
2198
+ );
2199
+
2200
+ if (selectAll) {
2201
+ const allColumns = statement.columns;
2202
+
2203
+ return allColumns;
2204
+ }
2205
+
2206
+ return statement.columns;
2207
+ }
2208
+
2209
+ // This function orders the rows
2210
+ // Please note: we use jsstore for sorting simply because it has the logic built in
2211
+ // But we don't use it as a normal select from the database, we pass it the rows we already have
2212
+ async applySort(
2213
+ statement: SearchStatementState,
2214
+ rows: {
2215
+ [columnName: string]: RawResults;
2216
+ }[]
2217
+ ): Promise<
2218
+ {
2219
+ [columnName: string]: RawResults;
2220
+ }[]
2221
+ > {
2222
+ const sorts = statement.validSortings;
2223
+
2224
+ if (!sorts.length) {
2225
+ return rows;
2226
+ }
2227
+
2228
+ const jsStoreSorts: IOrderQuery[] = sorts.map((sort) => {
2229
+ const column = sort.column;
2230
+
2231
+ const jsStoreSort: IOrderQuery = {
2232
+ by: column.as,
2233
+ type:
2234
+ sort.direction === SortStatementDirection.Ascending
2235
+ ? 'asc'
2236
+ : 'desc'
2237
+ };
2238
+
2239
+ return jsStoreSort;
2240
+ });
2241
+
2242
+ // @ts-ignore It asks for properties that it doesn't need, like "meta.primaryKey"
2243
+ const sortedRows = await this.connection.select({
2244
+ store: rows,
2245
+ order: jsStoreSorts
2246
+ });
2247
+
2248
+ return sortedRows as {
2249
+ [columnName: string]: RawResults;
2250
+ }[];
2251
+ }
2252
+
2253
+ applyLimitAndOffset(
2254
+ statement: SearchStatementState,
2255
+ rows: {
2256
+ [columnName: string]: RawResults;
2257
+ }[]
2258
+ ): {
2259
+ [columnName: string]: RawResults;
2260
+ }[] {
2261
+ let offsetResults = rows;
2262
+
2263
+ if (
2264
+ !!statement.offset &&
2265
+ statement.offset.type === SearchStatementNodeType.LiteralValue &&
2266
+ typeof statement.offset.value === 'number'
2267
+ ) {
2268
+ const offset = statement.offset.value;
2269
+
2270
+ offsetResults = rows.slice(offset);
2271
+ }
2272
+
2273
+ if (
2274
+ !!statement.limit &&
2275
+ statement.limit.type === SearchStatementNodeType.LiteralValue &&
2276
+ typeof statement.limit.value === 'number'
2277
+ ) {
2278
+ const limit = statement.limit.value;
2279
+
2280
+ offsetResults = offsetResults.slice(0, limit);
2281
+ }
2282
+
2283
+ return offsetResults;
2284
+ }
2285
+
2286
+ async executeSearchStatementAsQuery(
2287
+ statement: SearchStatementState
2288
+ ): Promise<ISelectQueryResultV2 | null> {
2289
+ if (!statement) {
2290
+ return null;
2291
+ }
2292
+
2293
+ if (!statement.from) {
2294
+ // Check for literal values
2295
+ let row: {
2296
+ [columnName: string]: RawResults;
2297
+ } = {};
2298
+
2299
+ statement.validSelectedColumns.forEach((column) => {
2300
+ row[column.as] = column.literalValue?.flattenRawValue();
2301
+ });
2302
+
2303
+ const literalSelectionResult: ISelectQueryResultV2 = {
2304
+ columns: statement.validSelectedColumns,
2305
+ rows: [row],
2306
+ tableName: null,
2307
+ entity: null,
2308
+ total: 1
2309
+ };
2310
+
2311
+ return literalSelectionResult;
2312
+ }
2313
+
2314
+ let mainResults: {
2315
+ [columnName: string]: RawResults;
2316
+ }[] = [];
2317
+
2318
+ let mainEntity: DefinitionEntityState | null = null;
2319
+
2320
+ if (
2321
+ statement.from.source.type ===
2322
+ SearchStatementNodeType.NestedSearchStatement
2323
+ ) {
2324
+ const subQueryResult = await this.executeSearchStatementAsQuery(
2325
+ // @ts-ignore
2326
+ statement.from.source.statement
2327
+ );
2328
+
2329
+ mainResults = subQueryResult.rows;
2330
+ } else if (statement.from.source.type === EntityType.DefinitionEntity) {
2331
+ const mainSourceResults = await this.select({
2332
+ from: toPascalCase(statement.from.source.name)
2333
+ });
2334
+
2335
+ mainResults = mainSourceResults;
2336
+ mainEntity = statement.from.source;
2337
+ } else if (
2338
+ statement.from.sourceType === DataSourceType.Function &&
2339
+ statement.from.functionCall
2340
+ ) {
2341
+ // --- NEW: EMULATE GENERATE_SERIES AND UNNEST ---
2342
+ const func = statement.from.functionCall;
2343
+ const funcName = func.functionName.toLowerCase();
2344
+ if (funcName === 'generate_series') {
2345
+ const start = Number(
2346
+ this.evaluateNode(func.arguments[0] as any, [])
2347
+ );
2348
+ const stop = Number(
2349
+ this.evaluateNode(func.arguments[1] as any, [])
2350
+ );
2351
+ const step = func.arguments[2]
2352
+ ? Number(this.evaluateNode(func.arguments[2] as any, []))
2353
+ : 1;
2354
+ for (let i = start; i <= stop; i += step) {
2355
+ mainResults.push({ [statement.from.as]: i });
2356
+ }
2357
+ } else if (funcName === 'unnest') {
2358
+ const arr = this.evaluateNode(func.arguments[0] as any, []);
2359
+ if (Array.isArray(arr)) {
2360
+ mainResults = arr.map((item) => ({
2361
+ [statement.from.as]: item
2362
+ }));
2363
+ }
2364
+ }
2365
+ }
2366
+
2367
+ const aggregationResults: {
2368
+ aggregation: AggregationStatement;
2369
+ results: {
2370
+ [columnName: string]: number | string | boolean | null | Date;
2371
+ }[];
2372
+ }[] = !!statement.from
2373
+ ? await Promise.all(
2374
+ statement.aggregations.map(async (aggregation) => {
2375
+ if (
2376
+ aggregation.dataSource?.source?.type ===
2377
+ SearchStatementNodeType.NestedSearchStatement
2378
+ ) {
2379
+ const results =
2380
+ await this.executeSearchStatementAsQuery(
2381
+ // @ts-ignore
2382
+ aggregation.dataSource?.source?.statement
2383
+ );
2384
+
2385
+ return {
2386
+ aggregation,
2387
+ results:
2388
+ (results?.rows as {
2389
+ [columnName: string]:
2390
+ | number
2391
+ | string
2392
+ | boolean
2393
+ | null
2394
+ | Date;
2395
+ }[]) || []
2396
+ };
2397
+ } else if (
2398
+ aggregation.dataSource?.source?.type ===
2399
+ EntityType.DefinitionEntity
2400
+ ) {
2401
+ const results = await this.select({
2402
+ from: toPascalCase(
2403
+ aggregation.dataSource?.source?.name
2404
+ )
2405
+ });
2406
+
2407
+ return {
2408
+ aggregation,
2409
+ results: results as {
2410
+ [columnName: string]:
2411
+ | number
2412
+ | string
2413
+ | boolean
2414
+ | null
2415
+ | Date;
2416
+ }[]
2417
+ };
2418
+ }
2419
+
2420
+ return {
2421
+ aggregation,
2422
+ results: []
2423
+ };
2424
+ })
2425
+ )
2426
+ : [];
2427
+
2428
+ const joinedResults = await this.applyJoinResults(
2429
+ statement,
2430
+ mainResults,
2431
+ aggregationResults
2432
+ );
2433
+
2434
+ const filteredResults = !!statement.from
2435
+ ? await this.applyWhereToResults(statement, joinedResults)
2436
+ : joinedResults;
2437
+
2438
+ const mappedResults = this.mapResults(statement, filteredResults);
2439
+
2440
+ // Check if this is a pure table or there are aggregations or aliases
2441
+ if (!statement.isUntouchedEntity) {
2442
+ mainEntity = null;
2443
+ }
2444
+
2445
+ const sortedResults = await this.applySort(statement, mappedResults);
2446
+
2447
+ // Apply limit and offset
2448
+ const offsetResults = this.applyLimitAndOffset(
2449
+ statement,
2450
+ sortedResults
2451
+ );
2452
+
2453
+ const result = {
2454
+ columns: statement.validSelectedColumns,
2455
+ rows: offsetResults,
2456
+ tableName: !!mainEntity ? toPascalCase(mainEntity.name) : null,
2457
+ entity: mainEntity,
2458
+ total: offsetResults.length
2459
+ };
2460
+
2461
+ return result;
2462
+ }
2463
+
2464
+ async runStatement(
2465
+ statement: SearchStatementState,
2466
+ inputs: IDynamicValue[] = []
2467
+ ): Promise<ISelectQueryResultV2 | null> {
2468
+ // console.log('[runStatement] Running statement: ', statement);
2469
+ console.log(
2470
+ `[runStatement] Inputs:\n ${inputs
2471
+ .map(
2472
+ (d) =>
2473
+ `${d.valueOwner?.type} (${d.valueOwner?.id}) "${resolveEntityName(d.valueOwner as any, this.store.project?.logic)}": ${JSON.stringify(d.value?.value, null, 2)}`
2474
+ )
2475
+ .join('\n ')}`
2476
+ );
2477
+
2478
+ if (!statement) {
2479
+ return null;
2480
+ }
2481
+
2482
+ const queryString = searchStatementUtils.sanitizeQuery(
2483
+ statement.toQuery()
2484
+ );
2485
+
2486
+ if (!queryString) {
2487
+ return null;
2488
+ }
2489
+
2490
+ let workingStatement = statement;
2491
+
2492
+ if (!!inputs?.length) {
2493
+ const resolvedInputs = inputs.map((i) => {
2494
+ if (i.valueOwner?.type === EntityType.ValueDescriptor) {
2495
+ // Exchange the value-descriptor for its input-map counterpart
2496
+ const parentInputs = ((i.valueOwner?.parent as SearchState)
2497
+ ?.inputs || []) as InputMapState[];
2498
+
2499
+ const foundInputMap = parentInputs.find(
2500
+ (p) => p.declaration?.id === i.valueOwner?.id
2501
+ );
2502
+
2503
+ return {
2504
+ ...i,
2505
+ valueOwner: foundInputMap
2506
+ };
2507
+ }
2508
+
2509
+ return i;
2510
+ });
2511
+
2512
+ const query = this.resolveRawInterpolationValues(
2513
+ queryString,
2514
+ resolvedInputs
2515
+ );
2516
+
2517
+ console.log('Query:\n', query);
2518
+
2519
+ const sanitizedAST = SQLASTLib.parse(query, {
2520
+ dialect: 'sqlite',
2521
+ // These are optional:
2522
+ includeSpaces: true // Adds spaces/tabs
2523
+ // includeNewlines: true, // Adds newlines
2524
+ // includeComments: true, // Adds comments
2525
+ // includeRange: true, // Adds source code location data
2526
+ });
2527
+
2528
+ const newStatementConfig =
2529
+ searchStatementUtils.hydrateSearchStatementState(
2530
+ sanitizedAST,
2531
+ this.store.project.logic
2532
+ );
2533
+ workingStatement = newStatementConfig.state;
2534
+ }
2535
+
2536
+ const result =
2537
+ await this.executeSearchStatementAsQuery(workingStatement);
2538
+
2539
+ this.onGoingQueryCache = {};
2540
+
2541
+ return result;
2542
+ }
2543
+
2544
+ async processSelectQuery(
2545
+ selectStatement: SelectStmt
2546
+ ): Promise<ISelectQueryResult> {
2547
+ if (!selectStatement) {
2548
+ return null;
2549
+ }
2550
+
2551
+ // @ts-ignore
2552
+ if (this.onGoingQueryCache[selectStatement.id]) {
2553
+ // @ts-ignore
2554
+ return this.onGoingQueryCache[selectStatement.id];
2555
+ }
2556
+
2557
+ let queryBase!:
2558
+ | {
2559
+ store: {
2560
+ [key: string]: number | string | boolean | null | Date;
2561
+ }[];
2562
+ meta: {
2563
+ primaryKey: string;
2564
+ };
2565
+ }
2566
+ | {
2567
+ from: string;
2568
+ };
2569
+
2570
+ const mainSource =
2571
+ searchStatementUtils.getMainSourceFromSelectStatement(
2572
+ selectStatement
2573
+ );
2574
+ const selectClause =
2575
+ searchStatementUtils.getSelectClauseFromSelectStatement(
2576
+ selectStatement
2577
+ );
2578
+ const ownTableMappings: searchStatementUtils.IShallowColumnMapping[] =
2579
+ searchStatementUtils.fromSelectClauseColumnsToColumnMappings(
2580
+ selectClause,
2581
+ (mainSource as any)?.name || ''
2582
+ // (mainSource as any).name === 'Product'
2583
+ );
2584
+
2585
+ const otherNewMappingsInTopSelect: searchStatementUtils.IShallowColumnMapping[] =
2586
+ searchStatementUtils
2587
+ .fromSelectClauseColumnsToColumnMappings(selectClause)
2588
+ .filter(
2589
+ (mapping) =>
2590
+ !ownTableMappings.find(
2591
+ (ownMapping) => ownMapping.from === mapping.from
2592
+ )
2593
+ );
2594
+
2595
+ let ownColumns: searchStatementUtils.IColumnMapping[] = [];
2596
+ let mainEntity: DefinitionEntityState = null;
2597
+
2598
+ if (mainSource?.type === 'table') {
2599
+ queryBase = {
2600
+ from: mainSource.name
2601
+ };
2602
+
2603
+ const table = this.getTable(mainSource.name);
2604
+
2605
+ mainEntity = table.entity;
2606
+
2607
+ ownColumns = table.columns.map((column) => {
2608
+ const mapping = ownTableMappings.find(
2609
+ (mapping) => mapping.from === column.columnName
2610
+ );
2611
+
2612
+ return {
2613
+ column: column,
2614
+ as: mapping?.to || column.columnName
2615
+ };
2616
+ });
2617
+ } else if (mainSource?.type === 'query' && !!mainSource.subquery) {
2618
+ // Execute the 'from' subquery and get the result
2619
+ const subQueryResult = await this.processSelectQuery(
2620
+ mainSource.subquery
2621
+ );
2622
+ mainEntity = subQueryResult.entity;
2623
+
2624
+ queryBase = {
2625
+ store: subQueryResult.rows,
2626
+ meta: {
2627
+ primaryKey: subQueryResult.columns.find(
2628
+ (column) => column.column.primaryKey
2629
+ ).column.columnName
2630
+ }
2631
+ };
2632
+
2633
+ ownColumns = subQueryResult.columns.map((column) => {
2634
+ const mapping = ownTableMappings.find(
2635
+ (mapping) => mapping.from === column.as
2636
+ );
2637
+
2638
+ return {
2639
+ column: column.column,
2640
+ as: mapping?.to || column.as
2641
+ };
2642
+ });
2643
+ } else {
2644
+ // Not supported
2645
+ return {
2646
+ columns: [],
2647
+ rows: [],
2648
+ tableName: null,
2649
+ entity: null,
2650
+ total: 0
2651
+ };
2652
+ }
2653
+
2654
+ let selectQuery: ISelectQuery = queryBase as ISelectQuery;
2655
+
2656
+ const offsetClause: LimitClause | null =
2657
+ searchStatementUtils.getLimitClauseFromSelectStatement(
2658
+ selectStatement
2659
+ );
2660
+
2661
+ const rawOffset: number =
2662
+ searchStatementUtils.getRawOffetFromLimitClause(
2663
+ offsetClause
2664
+ ) as number;
2665
+ const rawLimit: number | null =
2666
+ searchStatementUtils.getRawLimitFromLimitClause(offsetClause) as
2667
+ | number
2668
+ | null;
2669
+
2670
+ const orderByClause =
2671
+ searchStatementUtils.getOrderByClauseFromSelectStatement(
2672
+ selectStatement
2673
+ );
2674
+
2675
+ const joinClauses = await getJoinClausesFromSelectStatement(
2676
+ selectStatement,
2677
+ {
2678
+ onNestedSelect: this.processSelectQuery,
2679
+ onDescribeTable: async (tableName) => this.getTable(tableName)
2680
+ }
2681
+ );
2682
+
2683
+ if (rawLimit !== null) {
2684
+ selectQuery.limit = rawLimit;
2685
+ }
2686
+
2687
+ if (rawOffset) {
2688
+ selectQuery.skip = rawOffset;
2689
+ }
2690
+
2691
+ if (orderByClause) {
2692
+ selectQuery.order = await fromOrderByClauseToJsStore(
2693
+ orderByClause,
2694
+ selectStatement,
2695
+ {
2696
+ onNestedSelect: this.processSelectQuery,
2697
+ onDescribeTable: async (tableName) =>
2698
+ this.getTable(tableName)
2699
+ }
2700
+ );
2701
+ }
2702
+
2703
+ if (!!joinClauses.joins.length) {
2704
+ selectQuery.join = joinClauses.joins;
2705
+ }
2706
+
2707
+ const allSelectedColumnNames =
2708
+ getAllSelectedColumnNamesFromSelectClause(selectClause);
2709
+
2710
+ const builtInPrimaryKeyPropertyId =
2711
+ BUILT_IN_BASE_ENTITY_IDS[EntityType.BuiltInBaseEntity][
2712
+ BaseEntityNames.PERSISTED_ENTITY
2713
+ ].properties.primaryKey.id;
2714
+
2715
+ let primaryKeyName = mainEntity.properties.find(
2716
+ (property) =>
2717
+ property.id === builtInPrimaryKeyPropertyId ||
2718
+ property.implements.find(
2719
+ (impl) => impl.id === builtInPrimaryKeyPropertyId
2720
+ )
2721
+ )?.name;
2722
+
2723
+ const allColumns: searchStatementUtils.IColumnMapping[] = [
2724
+ ...ownColumns,
2725
+ ...joinClauses.columnMappings
2726
+ // Filter out duplicates
2727
+ ].filter((column, index, self) => {
2728
+ return self.findIndex((c) => c.as === column.as) === index;
2729
+ });
2730
+
2731
+ // We need to filter and rename any columns that have changed or aren't selected in t he querys
2732
+ const resultingQueryColumns: searchStatementUtils.IColumnMapping[] =
2733
+ allColumns.reduce((acc, columnMap) => {
2734
+ const alreadyExists = acc.find((c) => c.as === columnMap.as);
2735
+
2736
+ if (alreadyExists) {
2737
+ return acc;
2738
+ }
2739
+
2740
+ const newlyMapped = otherNewMappingsInTopSelect.find(
2741
+ (mapping) => mapping.from === columnMap.as
2742
+ );
2743
+
2744
+ columnMap.as = newlyMapped?.to || columnMap.as;
2745
+
2746
+ const selected =
2747
+ allSelectedColumnNames.includes(
2748
+ columnMap.column.columnName
2749
+ ) || allSelectedColumnNames.includes(columnMap?.as);
2750
+
2751
+ if (columnMap?.column.columnName === primaryKeyName) {
2752
+ primaryKeyName = columnMap.as;
2753
+ } else if (!primaryKeyName && columnMap.column.primaryKey) {
2754
+ primaryKeyName = columnMap.as;
2755
+ }
2756
+
2757
+ if (selected) {
2758
+ return [...acc, columnMap];
2759
+ } else {
2760
+ return acc;
2761
+ }
2762
+ }, [] as searchStatementUtils.IColumnMapping[]);
2763
+
2764
+ const whereClause =
2765
+ searchStatementUtils.getWhereClauseFromSelectStatement(
2766
+ selectStatement
2767
+ );
2768
+
2769
+ if (whereClause) {
2770
+ const resolvedWhereQuery = fromWhereClauseToJsStoreWhereObject(
2771
+ whereClause,
2772
+ allColumns
2773
+ );
2774
+
2775
+ if (resolvedWhereQuery && Object.keys(resolvedWhereQuery).length) {
2776
+ selectQuery.where = resolvedWhereQuery;
2777
+ }
2778
+ }
2779
+
2780
+ const rows: {
2781
+ [columnName: string]: any;
2782
+ }[] = await this.connection.select(selectQuery);
2783
+
2784
+ const filteredRows = await this.processFiltering(selectStatement, rows);
2785
+
2786
+ const count = filteredRows.length;
2787
+
2788
+ const mappedRows = filteredRows.map((row) => {
2789
+ const newRow: { [columnName: string]: any } = {
2790
+ ...row
2791
+ };
2792
+
2793
+ Object.keys(row).forEach((columnName) => {
2794
+ const columnMapping = resultingQueryColumns.find((mapping) => {
2795
+ // Split the column name by the dot
2796
+ const columnNameParts =
2797
+ mapping.column.columnName.split('.');
2798
+ const columnNamePart =
2799
+ columnNameParts[columnNameParts.length - 1];
2800
+
2801
+ if (
2802
+ mapping.column.columnName === columnName ||
2803
+ columnNamePart === columnName
2804
+ ) {
2805
+ return true;
2806
+ }
2807
+
2808
+ return false;
2809
+ });
2810
+
2811
+ if (columnMapping) {
2812
+ newRow[columnMapping.as] = row[columnName];
2813
+ } else {
2814
+ newRow[columnName] = row[columnName];
2815
+ }
2816
+ });
2817
+
2818
+ // Remove any property of the row that isn't present in the 'allSelectedColumnNames'
2819
+ Object.keys(newRow).forEach((columnName) => {
2820
+ if (!allSelectedColumnNames.includes(columnName)) {
2821
+ delete newRow[columnName];
2822
+ }
2823
+ });
2824
+
2825
+ return newRow;
2826
+ });
2827
+
2828
+ const isPureTable =
2829
+ mainSource.type === 'table' &&
2830
+ !(selectQuery.join as [])?.length &&
2831
+ resultingQueryColumns.every(
2832
+ (column) =>
2833
+ column.as === column.column.columnName &&
2834
+ column.column.table.tableName === mainSource.name
2835
+ );
2836
+
2837
+ const tableName = isPureTable ? mainSource.name : null;
2838
+
2839
+ const entity =
2840
+ mainSource.type === 'table'
2841
+ ? this.getTable(tableName)?.entity || null
2842
+ : null;
2843
+
2844
+ const result: ISelectQueryResult = {
2845
+ columns: resultingQueryColumns,
2846
+ rows: mappedRows,
2847
+ tableName: tableName,
2848
+ entity: entity,
2849
+ total: count
2850
+ };
2851
+
2852
+ // Cache the result
2853
+ // @ts-ignore
2854
+ this.onGoingQueryCache[selectStatement.id] = result;
2855
+
2856
+ return result;
2857
+ }
2858
+
2859
+ resolveRawInterpolationValues(
2860
+ query: string,
2861
+ inputs: IDynamicValue[]
2862
+ ): string {
2863
+ let queryString = query;
2864
+
2865
+ inputs.forEach((value) => {
2866
+ let valuetoReplaceWithQuotes = `'{{::${value.valueOwner.id}}}'`;
2867
+ let valuetoReplaceWithoutQuotes = `{{::${value.valueOwner.id}}}`;
2868
+
2869
+ let stringifiedValue = '';
2870
+
2871
+ // Use the literal's flattened raw value when available so Array
2872
+ // literals (whose `value` field carries nested
2873
+ // SearchStatementLiteralValue objects) collapse down to plain
2874
+ // primitives / nested arrays before we serialise them. Falling
2875
+ // back to value.value.value preserves behaviour for inputs whose
2876
+ // .value isn't a SearchStatementLiteralValue instance.
2877
+ const innerWrapper: any = value?.value;
2878
+ const flattened =
2879
+ innerWrapper &&
2880
+ typeof innerWrapper.flattenRawValue === 'function'
2881
+ ? innerWrapper.flattenRawValue()
2882
+ : innerWrapper?.value;
2883
+
2884
+ const literalValue = flattened;
2885
+
2886
+ if (
2887
+ !value ||
2888
+ !innerWrapper ||
2889
+ (literalValue == null &&
2890
+ literalValue !== false &&
2891
+ literalValue !== 0)
2892
+ ) {
2893
+ stringifiedValue = 'NULL';
2894
+ } else if (typeof literalValue === 'string') {
2895
+ stringifiedValue = `'${literalValue}'`;
2896
+ } else if (typeof literalValue === 'boolean') {
2897
+ stringifiedValue = `${literalValue}`;
2898
+ } else if (typeof literalValue === 'number') {
2899
+ stringifiedValue = `${literalValue}`;
2900
+ } else if (Array.isArray(literalValue)) {
2901
+ // Arrays interpolate as JSON-string arrays — same shape produced
2902
+ // by SearchStatementLiteralValue.toQuery() for Array literals,
2903
+ // so they round-trip through unnest()/array-typed function args
2904
+ // without further coercion.
2905
+ stringifiedValue = `'${JSON.stringify(literalValue)}'`;
2906
+ }
2907
+
2908
+ // @ts-ignore
2909
+ queryString = queryString.replaceAll(
2910
+ valuetoReplaceWithQuotes,
2911
+ stringifiedValue
2912
+ );
2913
+
2914
+ // @ts-ignore
2915
+ queryString = queryString.replaceAll(
2916
+ valuetoReplaceWithoutQuotes,
2917
+ stringifiedValue
2918
+ );
2919
+ });
2920
+
2921
+ return queryString;
2922
+ }
2923
+
2924
+ async run(
2925
+ sql: Program,
2926
+ inputs: IDynamicValue[] = []
2927
+ ): Promise<ISelectQueryResult> {
2928
+ toLower(sql);
2929
+
2930
+ let queryString = this.resolveRawInterpolationValues(
2931
+ SQLASTLib.show(sql),
2932
+ inputs
2933
+ );
2934
+
2935
+ const sanitizedAST = SQLASTLib.parse(queryString, {
2936
+ dialect: 'sqlite',
2937
+ // These are optional:
2938
+ includeSpaces: true // Adds spaces/tabs
2939
+ // includeNewlines: true, // Adds newlines
2940
+ // includeComments: true, // Adds comments
2941
+ // includeRange: true, // Adds source code location data
2942
+ });
2943
+
2944
+ const selectStatement =
2945
+ searchStatementUtils.getSelectStatementFromProgram(sanitizedAST);
2946
+
2947
+ let result: ISelectQueryResult = null;
2948
+
2949
+ if (!!selectStatement) {
2950
+ result = await this.processSelectQuery(selectStatement);
2951
+ }
2952
+
2953
+ this.onGoingQueryCache = {};
2954
+
2955
+ return result;
2956
+ }
2957
+ }
2958
+
2959
+ /*
2960
+ This class is responsible for managing the local indexed databases for the project
2961
+ and is the main interface for interacting with them
2962
+
2963
+ The constructor takes a project state
2964
+ Then searches for any instances of custom entities that extend the base 'relational database' entity
2965
+ For each database entity, it initializes (creates or reuses) a local indexed database
2966
+
2967
+ The class holds a list of active local dbs as well as utilities to interact with them
2968
+ */
2969
+ export class LocalRelationalDatabasesStore {
2970
+ project: EditorService;
2971
+
2972
+ databases: Database[] = [];
2973
+
2974
+ constructor(project: EditorService) {
2975
+ this.project = project;
2976
+
2977
+ this.initDb = this.initDb.bind(this);
2978
+ this.init = this.init.bind(this);
2979
+ this.destroy = this.destroy.bind(this);
2980
+ this.addDatabase = this.addDatabase.bind(this);
2981
+ this.getDatabase = this.getDatabase.bind(this);
2982
+ this.getDatabaseForPersistedEntity =
2983
+ this.getDatabaseForPersistedEntity.bind(this);
2984
+ this.onPersistedEntityAdded = this.onPersistedEntityAdded.bind(this);
2985
+ this.onPersistedEntityRemoved =
2986
+ this.onPersistedEntityRemoved.bind(this);
2987
+ this.onPersistedEntityUpdated =
2988
+ this.onPersistedEntityUpdated.bind(this);
2989
+ this.onDatabaseEntityAdded = this.onDatabaseEntityAdded.bind(this);
2990
+ this.onDatabaseEntityRemoved = this.onDatabaseEntityRemoved.bind(this);
2991
+ this.onDatabaseEntityUpdated = this.onDatabaseEntityUpdated.bind(this);
2992
+ }
2993
+
2994
+ // These are the user declared entities that represent a relational db
2995
+ get databaseEntities(): DefinitionEntityState[] {
2996
+ return this.project.logic.entities.filter((entity) =>
2997
+ checkHasBaseEntity(entity, BaseEntityNames.RELATIONAL_DATABASE)
2998
+ );
2999
+ }
3000
+
3001
+ // These are the user declared entities that are stored in the relational db as a table
3002
+ get persistedEntities(): DefinitionEntityState[] {
3003
+ return this.project.logic.entities.filter((entity) =>
3004
+ checkHasBaseEntity(entity, BaseEntityNames.PERSISTED_ENTITY)
3005
+ );
3006
+ }
3007
+
3008
+ onPersistedEntityAdded(entity: DefinitionEntityState) {
3009
+ if (checkHasBaseEntity(entity, BaseEntityNames.PERSISTED_ENTITY)) {
3010
+ if (!this.persistedEntities.find((e) => e.id === entity.id)) {
3011
+ this.persistedEntities.push(entity);
3012
+ }
3013
+
3014
+ // Find and then sync the parent db
3015
+ const db = this.getDatabaseForPersistedEntity(entity);
3016
+
3017
+ if (db) {
3018
+ this.initDb(db.entity);
3019
+ }
3020
+ }
3021
+ }
3022
+
3023
+ onPersistedEntityRemoved(entity: DefinitionEntityState) {
3024
+ if (checkHasBaseEntity(entity, BaseEntityNames.PERSISTED_ENTITY)) {
3025
+ // Find and then sync the parent db
3026
+ const db = this.getDatabaseForPersistedEntity(entity);
3027
+
3028
+ if (db) {
3029
+ this.initDb(db.entity);
3030
+ }
3031
+ }
3032
+ }
3033
+
3034
+ onPersistedEntityUpdated(entity: DefinitionEntityState) {
3035
+ if (checkHasBaseEntity(entity, BaseEntityNames.PERSISTED_ENTITY)) {
3036
+ // Find and then sync the parent db
3037
+ const db = this.getDatabaseForPersistedEntity(entity);
3038
+
3039
+ if (db) {
3040
+ this.initDb(db.entity);
3041
+ }
3042
+ }
3043
+ }
3044
+
3045
+ onDatabaseEntityAdded(entity: DefinitionEntityState) {
3046
+ if (checkHasBaseEntity(entity, BaseEntityNames.RELATIONAL_DATABASE)) {
3047
+ if (!this.databaseEntities.find((e) => e.id === entity.id)) {
3048
+ this.databaseEntities.push(entity);
3049
+ }
3050
+
3051
+ // Improve syncing of the db
3052
+ this.initDb(entity);
3053
+ }
3054
+ }
3055
+
3056
+ onDatabaseEntityRemoved(entity: DefinitionEntityState) {
3057
+ if (checkHasBaseEntity(entity, BaseEntityNames.RELATIONAL_DATABASE)) {
3058
+ const db = this.getDatabase(entity.id);
3059
+
3060
+ if (db) {
3061
+ this.databases = this.databases.filter(
3062
+ (db) => db.entity.id !== entity.id
3063
+ );
3064
+ db.destroy();
3065
+ }
3066
+ }
3067
+ }
3068
+
3069
+ onDatabaseEntityUpdated(entity: DefinitionEntityState) {
3070
+ // Find the db and reinitialize it
3071
+ // if the name is what has changed, delete the old db and create a new one
3072
+ const db = this.getDatabase(entity.id);
3073
+
3074
+ if (db) {
3075
+ const dbName = Database.toDbName(entity);
3076
+
3077
+ if (db.connection?.database?.name !== dbName) {
3078
+ db.destroy();
3079
+ }
3080
+
3081
+ this.initDb(entity);
3082
+ }
3083
+ }
3084
+
3085
+ async init(): Promise<void> {
3086
+ await Promise.all(this.databaseEntities.map(this.initDb));
3087
+
3088
+ // Listen for changes to the project state
3089
+ // And modify the databases accordingly
3090
+ this.project.logic.on(
3091
+ ProjectStateEvents.PERSISTED_DEFINITION_ENTITY_ADDED,
3092
+ this.onPersistedEntityAdded
3093
+ );
3094
+ this.project.logic.on(
3095
+ ProjectStateEvents.PERSISTED_DEFINITION_ENTITY_REMOVED,
3096
+ this.onPersistedEntityRemoved
3097
+ );
3098
+ this.project.logic.on(
3099
+ ProjectStateEvents.PERSISTED_DEFINITION_ENTITY_UPDATED,
3100
+ this.onPersistedEntityUpdated
3101
+ );
3102
+
3103
+ this.project.logic.on(
3104
+ ProjectStateEvents.RELATIONAL_DATABASE_DEFINITION_ENTITY_ADDED,
3105
+ this.onDatabaseEntityAdded
3106
+ );
3107
+ this.project.logic.on(
3108
+ ProjectStateEvents.RELATIONAL_DATABASE_DEFINITION_ENTITY_REMOVED,
3109
+ this.onDatabaseEntityRemoved
3110
+ );
3111
+ this.project.logic.on(
3112
+ ProjectStateEvents.RELATIONAL_DATABASE_DEFINITION_ENTITY_UPDATED,
3113
+ this.onDatabaseEntityUpdated
3114
+ );
3115
+ }
3116
+
3117
+ async destroy(): Promise<void> {
3118
+ await Promise.all(this.databases.map((db) => db.disconnect()));
3119
+
3120
+ this.databases = [];
3121
+ this.project = null;
3122
+
3123
+ this.project?.logic?.off(
3124
+ ProjectStateEvents.PERSISTED_DEFINITION_ENTITY_ADDED,
3125
+ this.onPersistedEntityAdded
3126
+ );
3127
+ this.project?.logic?.off(
3128
+ ProjectStateEvents.PERSISTED_DEFINITION_ENTITY_REMOVED,
3129
+ this.onPersistedEntityRemoved
3130
+ );
3131
+ this.project?.logic?.off(
3132
+ ProjectStateEvents.PERSISTED_DEFINITION_ENTITY_UPDATED,
3133
+ this.onPersistedEntityUpdated
3134
+ );
3135
+ this.project?.logic?.off(
3136
+ ProjectStateEvents.RELATIONAL_DATABASE_DEFINITION_ENTITY_ADDED,
3137
+ this.onDatabaseEntityAdded
3138
+ );
3139
+ this.project?.logic?.off(
3140
+ ProjectStateEvents.RELATIONAL_DATABASE_DEFINITION_ENTITY_REMOVED,
3141
+ this.onDatabaseEntityRemoved
3142
+ );
3143
+ this.project?.logic?.off(
3144
+ ProjectStateEvents.RELATIONAL_DATABASE_DEFINITION_ENTITY_UPDATED,
3145
+ this.onDatabaseEntityUpdated
3146
+ );
3147
+ }
3148
+
3149
+ async initDb(entity: DefinitionEntityState): Promise<Database> {
3150
+ const database =
3151
+ this.getDatabase(entity.id) || new Database(entity, this);
3152
+
3153
+ try {
3154
+ await database.init();
3155
+ } catch (e) {
3156
+ console.error(
3157
+ `Error initializing local db for ${resolveEntityName(
3158
+ entity,
3159
+ this.project.logic
3160
+ )}: `,
3161
+ e
3162
+ );
3163
+ }
3164
+
3165
+ const savedDatabase = this.addDatabase(database);
3166
+
3167
+ return savedDatabase;
3168
+ }
3169
+
3170
+ addDatabase(database: Database): Database {
3171
+ let existingDatabase: Database = this.databases.find(
3172
+ (db) =>
3173
+ db === database ||
3174
+ db.connection === database.connection ||
3175
+ db.connection?.database?.name ===
3176
+ database.connection?.database?.name ||
3177
+ db.entity.id === database.entity.id
3178
+ );
3179
+
3180
+ // If it doesn't yet exist, add it to the list of active dbs
3181
+ if (!existingDatabase) {
3182
+ this.databases.push(database);
3183
+
3184
+ return database;
3185
+ }
3186
+
3187
+ return existingDatabase;
3188
+ }
3189
+
3190
+ // Given a definition entity that extends the built in persisted entity
3191
+ // It returns the database instance that owns the table for this entity
3192
+ getDatabaseForPersistedEntity(entity: DefinitionEntityState): Database {
3193
+ const databaseEntity =
3194
+ resolvePersistedDefinitionEntityDatabaseEntity(entity);
3195
+
3196
+ if (!databaseEntity) {
3197
+ return null;
3198
+ }
3199
+
3200
+ const database = this.databases.find((db) => {
3201
+ return db.entity.id === databaseEntity.id;
3202
+ });
3203
+
3204
+ return database;
3205
+ }
3206
+
3207
+ getDatabase(entityId: string): Database {
3208
+ return this.databases.find((db) => db.entity.id === entityId);
3209
+ }
3210
+ }
3211
+
3212
+ export async function browserSearchImplementation(
3213
+ entity: SearchState,
3214
+ inputs: IDynamicValue[],
3215
+ project: EditorService
3216
+ ): Promise<{
3217
+ // @ts-ignore
3218
+ error: string | null;
3219
+ [dataPropertyName: string]:
3220
+ | {
3221
+ [columnName: string]: any;
3222
+ }
3223
+ | null
3224
+ | {
3225
+ [columnName: string]: any;
3226
+ }[];
3227
+ }> {
3228
+ const initialAst = searchStatementUtils.sanitizeQuery(entity.query)
3229
+ ? SQLASTLib.parse(searchStatementUtils.sanitizeQuery(entity.query), {
3230
+ dialect: 'sqlite'
3231
+ })
3232
+ : null;
3233
+
3234
+ const hydratedStateConfig =
3235
+ searchStatementUtils.hydrateSearchStatementState(
3236
+ initialAst,
3237
+ project.logic,
3238
+ entity
3239
+ );
3240
+
3241
+ if (!!hydratedStateConfig.errors.length) {
3242
+ // Do nothing
3243
+ }
3244
+
3245
+ const searchStatementState = hydratedStateConfig.state;
3246
+
3247
+ // Based on the main data source, we resolve the database client
3248
+ const resolveDatabaseClient = (): Database | null => {
3249
+ if (!!searchStatementState?.from) {
3250
+ const mainDataSourceEntity =
3251
+ searchStatementState?.mainPersistedEntity || null;
3252
+
3253
+ if (!mainDataSourceEntity) {
3254
+ return null;
3255
+ }
3256
+
3257
+ const database =
3258
+ project?.localDatabaseStore?.getDatabaseForPersistedEntity(
3259
+ mainDataSourceEntity
3260
+ );
3261
+
3262
+ return database;
3263
+ } else {
3264
+ // There is no need for any table to be present, so we just need ANY client, to query against
3265
+ return project?.localDatabaseStore?.databases[0] || null;
3266
+ }
3267
+ };
3268
+
3269
+ const dbClient = resolveDatabaseClient();
3270
+
3271
+ const newResult: ISelectQueryResultV2 = (await dbClient?.runStatement(
3272
+ searchStatementState,
3273
+ inputs
3274
+ )) as ISelectQueryResultV2;
3275
+
3276
+ const newValue = searchStatementState.asList
3277
+ ? newResult?.rows
3278
+ : newResult?.rows[0] || null;
3279
+
3280
+ const resultDT = entity.getDataType(null);
3281
+ if (
3282
+ resultDT?.entity.type === EntityType.DefinitionEntity &&
3283
+ !!resultDT?.entity.properties[0]
3284
+ ) {
3285
+ const foundOutputMap = entity.outputs.find(
3286
+ (output) =>
3287
+ output.declaration?.id ===
3288
+ (resultDT.entity as DefinitionEntityState)?.properties[0].id
3289
+ );
3290
+
3291
+ const dataPropertyName = foundOutputMap
3292
+ ? foundOutputMap.codeName ||
3293
+ foundOutputMap.declaration?.codeName ||
3294
+ toCamelCase(resolveEntityName(foundOutputMap, entity.project))
3295
+ : toCamelCase(resolveEntityName(resultDT.entity, entity.project)) ||
3296
+ 'data';
3297
+
3298
+ return {
3299
+ [dataPropertyName]: newValue,
3300
+ error: null
3301
+ };
3302
+ }
3303
+
3304
+ return {
3305
+ data: newValue,
3306
+ error: null
3307
+ };
3308
+ }