@adecore/database 0.0.1 → 0.16.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (400) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +152 -1
  3. package/dist/DatabaseProvider.d.ts +13 -0
  4. package/dist/DatabaseProvider.js +17 -0
  5. package/dist/actions.d.ts +40 -0
  6. package/dist/actions.js +1 -0
  7. package/dist/client/client.d.ts +5 -0
  8. package/dist/client/client.js +245 -0
  9. package/dist/client/index.d.ts +2 -0
  10. package/dist/client/index.js +2 -0
  11. package/dist/client/types.d.ts +97 -0
  12. package/dist/client/types.js +13 -0
  13. package/dist/client-context.d.ts +16 -0
  14. package/dist/client-context.js +17 -0
  15. package/dist/connections/ConnectionForm.d.ts +10 -0
  16. package/dist/connections/ConnectionForm.js +145 -0
  17. package/dist/connections/ConnectionManager.d.ts +12 -0
  18. package/dist/connections/ConnectionManager.js +93 -0
  19. package/dist/connections/EngineIcon.d.ts +9 -0
  20. package/dist/connections/EngineIcon.js +7 -0
  21. package/dist/connections/connection-config.d.ts +16 -0
  22. package/dist/connections/connection-config.js +107 -0
  23. package/dist/connections/docker.d.ts +9 -0
  24. package/dist/connections/docker.js +38 -0
  25. package/dist/connections/engine-icons.d.ts +3 -0
  26. package/dist/connections/engine-icons.js +3 -0
  27. package/dist/connections/use-docker-containers.d.ts +17 -0
  28. package/dist/connections/use-docker-containers.js +26 -0
  29. package/dist/console/DestructiveDialog.d.ts +9 -0
  30. package/dist/console/DestructiveDialog.js +9 -0
  31. package/dist/console/HistoryPanel.d.ts +10 -0
  32. package/dist/console/HistoryPanel.js +15 -0
  33. package/dist/console/QueryConsole.d.ts +14 -0
  34. package/dist/console/QueryConsole.js +261 -0
  35. package/dist/console/SchemaPicker.d.ts +11 -0
  36. package/dist/console/SchemaPicker.js +8 -0
  37. package/dist/console/SqlEditor.d.ts +12 -0
  38. package/dist/console/SqlEditor.js +61 -0
  39. package/dist/console/StatementResultView.d.ts +18 -0
  40. package/dist/console/StatementResultView.js +32 -0
  41. package/dist/console/TransactionControls.d.ts +13 -0
  42. package/dist/console/TransactionControls.js +12 -0
  43. package/dist/console/destructive.d.ts +7 -0
  44. package/dist/console/destructive.js +43 -0
  45. package/dist/console/editing.d.ts +12 -0
  46. package/dist/console/editing.js +60 -0
  47. package/dist/console/history.d.ts +13 -0
  48. package/dist/console/history.js +43 -0
  49. package/dist/console/labels.d.ts +1 -0
  50. package/dist/console/labels.js +6 -0
  51. package/dist/console/outcome.d.ts +6 -0
  52. package/dist/console/outcome.js +11 -0
  53. package/dist/console/schemas.d.ts +2 -0
  54. package/dist/console/schemas.js +5 -0
  55. package/dist/console/shortcuts.d.ts +2 -0
  56. package/dist/console/shortcuts.js +3 -0
  57. package/dist/console/useConsoleHistory.d.ts +7 -0
  58. package/dist/console/useConsoleHistory.js +22 -0
  59. package/dist/ddl/alter-mysql.d.ts +4 -0
  60. package/dist/ddl/alter-mysql.js +79 -0
  61. package/dist/ddl/alter-sqlite.d.ts +4 -0
  62. package/dist/ddl/alter-sqlite.js +80 -0
  63. package/dist/ddl/alter.d.ts +4 -0
  64. package/dist/ddl/alter.js +24 -0
  65. package/dist/ddl/column.d.ts +9 -0
  66. package/dist/ddl/column.js +62 -0
  67. package/dist/ddl/create.d.ts +12 -0
  68. package/dist/ddl/create.js +74 -0
  69. package/dist/ddl/dialect.d.ts +12 -0
  70. package/dist/ddl/dialect.js +34 -0
  71. package/dist/ddl/diff.d.ts +34 -0
  72. package/dist/ddl/diff.js +111 -0
  73. package/dist/ddl/draft.d.ts +46 -0
  74. package/dist/ddl/draft.js +70 -0
  75. package/dist/ddl/index.d.ts +10 -0
  76. package/dist/ddl/index.js +10 -0
  77. package/dist/ddl/parse.d.ts +17 -0
  78. package/dist/ddl/parse.js +131 -0
  79. package/dist/ddl/statements.d.ts +6 -0
  80. package/dist/ddl/statements.js +12 -0
  81. package/dist/ddl/types.d.ts +6 -0
  82. package/dist/ddl/types.js +52 -0
  83. package/dist/ddl/validate.d.ts +7 -0
  84. package/dist/ddl/validate.js +38 -0
  85. package/dist/designer/ColumnsEditor.d.ts +2 -0
  86. package/dist/designer/ColumnsEditor.js +24 -0
  87. package/dist/designer/DesignerView.d.ts +25 -0
  88. package/dist/designer/DesignerView.js +33 -0
  89. package/dist/designer/ForeignKeysEditor.d.ts +7 -0
  90. package/dist/designer/ForeignKeysEditor.js +24 -0
  91. package/dist/designer/IndexesEditor.d.ts +2 -0
  92. package/dist/designer/IndexesEditor.js +13 -0
  93. package/dist/designer/NamePicker.d.ts +11 -0
  94. package/dist/designer/NamePicker.js +13 -0
  95. package/dist/designer/OptionsEditor.d.ts +2 -0
  96. package/dist/designer/OptionsEditor.js +17 -0
  97. package/dist/designer/SqlPreview.d.ts +12 -0
  98. package/dist/designer/SqlPreview.js +17 -0
  99. package/dist/designer/TableDesigner.d.ts +10 -0
  100. package/dist/designer/TableDesigner.js +103 -0
  101. package/dist/designer/edit.d.ts +17 -0
  102. package/dist/designer/edit.js +115 -0
  103. package/dist/designer/editor-props.d.ts +10 -0
  104. package/dist/designer/editor-props.js +1 -0
  105. package/dist/designer/plan.d.ts +9 -0
  106. package/dist/designer/plan.js +13 -0
  107. package/dist/designer/recover.d.ts +2 -0
  108. package/dist/designer/recover.js +18 -0
  109. package/dist/designer/useReferenceColumns.d.ts +3 -0
  110. package/dist/designer/useReferenceColumns.js +36 -0
  111. package/dist/explorer/DatabaseExplorer.d.ts +13 -0
  112. package/dist/explorer/DatabaseExplorer.js +264 -0
  113. package/dist/explorer/RowMenu.d.ts +11 -0
  114. package/dist/explorer/RowMenu.js +89 -0
  115. package/dist/explorer/TableDialog.d.ts +15 -0
  116. package/dist/explorer/TableDialog.js +50 -0
  117. package/dist/explorer/expansion.d.ts +7 -0
  118. package/dist/explorer/expansion.js +31 -0
  119. package/dist/explorer/table-statements.d.ts +5 -0
  120. package/dist/explorer/table-statements.js +12 -0
  121. package/dist/explorer/tree.d.ts +125 -0
  122. package/dist/explorer/tree.js +295 -0
  123. package/dist/explorer/use-explorer-loads.d.ts +12 -0
  124. package/dist/explorer/use-explorer-loads.js +109 -0
  125. package/dist/explorer/use-stored-expansion.d.ts +9 -0
  126. package/dist/explorer/use-stored-expansion.js +47 -0
  127. package/dist/grid/CellEditor.d.ts +9 -0
  128. package/dist/grid/CellEditor.js +29 -0
  129. package/dist/grid/CopyAsMenu.d.ts +7 -0
  130. package/dist/grid/CopyAsMenu.js +10 -0
  131. package/dist/grid/DataGrid.d.ts +36 -0
  132. package/dist/grid/DataGrid.js +512 -0
  133. package/dist/grid/EnumPicker.d.ts +17 -0
  134. package/dist/grid/EnumPicker.js +53 -0
  135. package/dist/grid/GridHeaderCell.d.ts +36 -0
  136. package/dist/grid/GridHeaderCell.js +69 -0
  137. package/dist/grid/RecordView.d.ts +20 -0
  138. package/dist/grid/RecordView.js +126 -0
  139. package/dist/grid/ValueDock.d.ts +11 -0
  140. package/dist/grid/ValueDock.js +28 -0
  141. package/dist/grid/aggregates.d.ts +22 -0
  142. package/dist/grid/aggregates.js +62 -0
  143. package/dist/grid/cell-range.d.ts +14 -0
  144. package/dist/grid/cell-range.js +11 -0
  145. package/dist/grid/column-layout.d.ts +18 -0
  146. package/dist/grid/column-layout.js +49 -0
  147. package/dist/grid/column-selection.d.ts +11 -0
  148. package/dist/grid/column-selection.js +28 -0
  149. package/dist/grid/column-view.d.ts +10 -0
  150. package/dist/grid/column-view.js +23 -0
  151. package/dist/grid/copy-formats.d.ts +15 -0
  152. package/dist/grid/copy-formats.js +80 -0
  153. package/dist/grid/display.d.ts +20 -0
  154. package/dist/grid/display.js +81 -0
  155. package/dist/grid/edit-value.d.ts +5 -0
  156. package/dist/grid/edit-value.js +55 -0
  157. package/dist/grid/enum-type.d.ts +7 -0
  158. package/dist/grid/enum-type.js +52 -0
  159. package/dist/grid/focused-value.d.ts +4 -0
  160. package/dist/grid/focused-value.js +19 -0
  161. package/dist/grid/layout.d.ts +22 -0
  162. package/dist/grid/layout.js +74 -0
  163. package/dist/grid/navigation.d.ts +15 -0
  164. package/dist/grid/navigation.js +52 -0
  165. package/dist/grid/row-selection.d.ts +9 -0
  166. package/dist/grid/row-selection.js +24 -0
  167. package/dist/grid/sort.d.ts +7 -0
  168. package/dist/grid/sort.js +7 -0
  169. package/dist/grid/types.d.ts +30 -0
  170. package/dist/grid/types.js +9 -0
  171. package/dist/host/helper-path.d.ts +7 -0
  172. package/dist/host/helper-path.js +15 -0
  173. package/dist/host/host.d.ts +2 -0
  174. package/dist/host/host.js +343 -0
  175. package/dist/host/index.d.ts +5 -0
  176. package/dist/host/index.js +4 -0
  177. package/dist/host/spawn.d.ts +7 -0
  178. package/dist/host/spawn.js +98 -0
  179. package/dist/host/types.d.ts +19 -0
  180. package/dist/host/types.js +1 -0
  181. package/dist/host/validate.d.ts +10 -0
  182. package/dist/host/validate.js +339 -0
  183. package/dist/index.d.ts +14 -0
  184. package/dist/index.js +13 -0
  185. package/dist/locales/en.json +577 -0
  186. package/dist/locales/nl.json +577 -0
  187. package/dist/locales.d.ts +4 -0
  188. package/dist/locales.js +14 -0
  189. package/dist/protocol/connection.d.ts +56 -0
  190. package/dist/protocol/connection.js +1 -0
  191. package/dist/protocol/errors.d.ts +7 -0
  192. package/dist/protocol/errors.js +1 -0
  193. package/dist/protocol/index.d.ts +5 -0
  194. package/dist/protocol/index.js +3 -0
  195. package/dist/protocol/messages.d.ts +250 -0
  196. package/dist/protocol/messages.js +2 -0
  197. package/dist/protocol/schema.d.ts +53 -0
  198. package/dist/protocol/schema.js +1 -0
  199. package/dist/protocol/values.d.ts +21 -0
  200. package/dist/protocol/values.js +15 -0
  201. package/dist/sql-split.d.ts +15 -0
  202. package/dist/sql-split.js +201 -0
  203. package/dist/sql.d.ts +29 -0
  204. package/dist/sql.js +104 -0
  205. package/dist/structure/StructureView.d.ts +14 -0
  206. package/dist/structure/StructureView.js +99 -0
  207. package/dist/structure/structure-text.d.ts +2 -0
  208. package/dist/structure/structure-text.js +2 -0
  209. package/dist/table/CommandField.d.ts +14 -0
  210. package/dist/table/CommandField.js +157 -0
  211. package/dist/table/ImportDialog.d.ts +27 -0
  212. package/dist/table/ImportDialog.js +28 -0
  213. package/dist/table/TableStatusBar.d.ts +19 -0
  214. package/dist/table/TableStatusBar.js +22 -0
  215. package/dist/table/TableToolbar.d.ts +43 -0
  216. package/dist/table/TableToolbar.js +16 -0
  217. package/dist/table/TableView.d.ts +13 -0
  218. package/dist/table/TableView.js +508 -0
  219. package/dist/table/command-field.d.ts +57 -0
  220. package/dist/table/command-field.js +203 -0
  221. package/dist/table/grid-rows.d.ts +23 -0
  222. package/dist/table/grid-rows.js +55 -0
  223. package/dist/table/import-mapping.d.ts +7 -0
  224. package/dist/table/import-mapping.js +28 -0
  225. package/dist/table/layout-store.d.ts +13 -0
  226. package/dist/table/layout-store.js +43 -0
  227. package/dist/table/paging.d.ts +12 -0
  228. package/dist/table/paging.js +20 -0
  229. package/dist/table/pending.d.ts +32 -0
  230. package/dist/table/pending.js +94 -0
  231. package/dist/table/references.d.ts +12 -0
  232. package/dist/table/references.js +21 -0
  233. package/dist/table/useLoaded.d.ts +7 -0
  234. package/dist/table/useLoaded.js +36 -0
  235. package/dist/table/useStoredLayout.d.ts +8 -0
  236. package/dist/table/useStoredLayout.js +39 -0
  237. package/dist/table/useTableTransfer.d.ts +51 -0
  238. package/dist/table/useTableTransfer.js +125 -0
  239. package/dist/testing/fake.d.ts +21 -0
  240. package/dist/testing/fake.js +372 -0
  241. package/dist/testing/index.d.ts +1 -0
  242. package/dist/testing/index.js +1 -0
  243. package/dist/use-popup-press.d.ts +1 -0
  244. package/dist/use-popup-press.js +24 -0
  245. package/dist/use-schema-change.d.ts +1 -0
  246. package/dist/use-schema-change.js +13 -0
  247. package/dist/use-stable-callback.d.ts +1 -0
  248. package/dist/use-stable-callback.js +9 -0
  249. package/dist/value/ValuePanel.d.ts +14 -0
  250. package/dist/value/ValuePanel.js +103 -0
  251. package/dist/value/draft.d.ts +33 -0
  252. package/dist/value/draft.js +44 -0
  253. package/dist/value/hex.d.ts +14 -0
  254. package/dist/value/hex.js +46 -0
  255. package/dist/value/json.d.ts +9 -0
  256. package/dist/value/json.js +71 -0
  257. package/dist/value/views.d.ts +22 -0
  258. package/dist/value/views.js +54 -0
  259. package/dist/workbench/DatabaseWorkbench.d.ts +9 -0
  260. package/dist/workbench/DatabaseWorkbench.js +179 -0
  261. package/dist/workbench/DesignerTab.d.ts +10 -0
  262. package/dist/workbench/DesignerTab.js +22 -0
  263. package/dist/workbench/TableTab.d.ts +10 -0
  264. package/dist/workbench/TableTab.js +8 -0
  265. package/dist/workbench/tabs.d.ts +42 -0
  266. package/dist/workbench/tabs.js +139 -0
  267. package/package.json +79 -3
  268. package/src/DatabaseProvider.tsx +37 -0
  269. package/src/actions.ts +34 -0
  270. package/src/client/client.ts +324 -0
  271. package/src/client/index.ts +18 -0
  272. package/src/client/types.ts +161 -0
  273. package/src/client-context.ts +35 -0
  274. package/src/connections/ConnectionForm.tsx +474 -0
  275. package/src/connections/ConnectionManager.tsx +224 -0
  276. package/src/connections/EngineIcon.tsx +18 -0
  277. package/src/connections/connection-config.ts +143 -0
  278. package/src/connections/docker.ts +50 -0
  279. package/src/connections/engine-icons.ts +5 -0
  280. package/src/connections/use-docker-containers.ts +41 -0
  281. package/src/console/DestructiveDialog.tsx +40 -0
  282. package/src/console/HistoryPanel.tsx +67 -0
  283. package/src/console/QueryConsole.tsx +439 -0
  284. package/src/console/SchemaPicker.tsx +32 -0
  285. package/src/console/SqlEditor.tsx +95 -0
  286. package/src/console/StatementResultView.tsx +176 -0
  287. package/src/console/TransactionControls.tsx +53 -0
  288. package/src/console/destructive.ts +60 -0
  289. package/src/console/editing.ts +77 -0
  290. package/src/console/history.ts +62 -0
  291. package/src/console/labels.ts +7 -0
  292. package/src/console/outcome.ts +14 -0
  293. package/src/console/schemas.ts +7 -0
  294. package/src/console/shortcuts.ts +4 -0
  295. package/src/console/useConsoleHistory.ts +39 -0
  296. package/src/ddl/alter-mysql.ts +90 -0
  297. package/src/ddl/alter-sqlite.ts +93 -0
  298. package/src/ddl/alter.ts +29 -0
  299. package/src/ddl/column.ts +74 -0
  300. package/src/ddl/create.ts +92 -0
  301. package/src/ddl/dialect.ts +53 -0
  302. package/src/ddl/diff.ts +164 -0
  303. package/src/ddl/draft.ts +136 -0
  304. package/src/ddl/index.ts +10 -0
  305. package/src/ddl/parse.ts +161 -0
  306. package/src/ddl/statements.ts +20 -0
  307. package/src/ddl/types.ts +60 -0
  308. package/src/ddl/validate.ts +47 -0
  309. package/src/designer/ColumnsEditor.tsx +204 -0
  310. package/src/designer/DesignerView.tsx +184 -0
  311. package/src/designer/ForeignKeysEditor.tsx +132 -0
  312. package/src/designer/IndexesEditor.tsx +63 -0
  313. package/src/designer/NamePicker.tsx +44 -0
  314. package/src/designer/OptionsEditor.tsx +101 -0
  315. package/src/designer/SqlPreview.tsx +56 -0
  316. package/src/designer/TableDesigner.tsx +186 -0
  317. package/src/designer/edit.ts +137 -0
  318. package/src/designer/editor-props.ts +13 -0
  319. package/src/designer/plan.ts +25 -0
  320. package/src/designer/recover.ts +20 -0
  321. package/src/designer/useReferenceColumns.ts +54 -0
  322. package/src/explorer/DatabaseExplorer.tsx +450 -0
  323. package/src/explorer/RowMenu.tsx +134 -0
  324. package/src/explorer/TableDialog.tsx +80 -0
  325. package/src/explorer/expansion.ts +47 -0
  326. package/src/explorer/table-statements.ts +18 -0
  327. package/src/explorer/tree.ts +453 -0
  328. package/src/explorer/use-explorer-loads.ts +157 -0
  329. package/src/explorer/use-stored-expansion.ts +66 -0
  330. package/src/grid/CellEditor.tsx +53 -0
  331. package/src/grid/CopyAsMenu.tsx +33 -0
  332. package/src/grid/DataGrid.tsx +871 -0
  333. package/src/grid/EnumPicker.tsx +108 -0
  334. package/src/grid/GridHeaderCell.tsx +243 -0
  335. package/src/grid/RecordView.tsx +293 -0
  336. package/src/grid/ValueDock.tsx +52 -0
  337. package/src/grid/aggregates.ts +94 -0
  338. package/src/grid/cell-range.ts +26 -0
  339. package/src/grid/column-layout.ts +71 -0
  340. package/src/grid/column-selection.ts +42 -0
  341. package/src/grid/column-view.ts +35 -0
  342. package/src/grid/copy-formats.ts +108 -0
  343. package/src/grid/display.ts +109 -0
  344. package/src/grid/edit-value.ts +60 -0
  345. package/src/grid/enum-type.ts +62 -0
  346. package/src/grid/focused-value.ts +23 -0
  347. package/src/grid/layout.ts +91 -0
  348. package/src/grid/navigation.ts +74 -0
  349. package/src/grid/row-selection.ts +35 -0
  350. package/src/grid/sort.ts +12 -0
  351. package/src/grid/types.ts +59 -0
  352. package/src/host/helper-path.ts +41 -0
  353. package/src/host/host.ts +456 -0
  354. package/src/host/index.ts +5 -0
  355. package/src/host/spawn.ts +124 -0
  356. package/src/host/types.ts +46 -0
  357. package/src/host/validate.ts +402 -0
  358. package/src/index.ts +37 -0
  359. package/src/locales/en.json +577 -0
  360. package/src/locales/nl.json +577 -0
  361. package/src/locales.ts +18 -0
  362. package/src/protocol/connection.ts +86 -0
  363. package/src/protocol/errors.ts +38 -0
  364. package/src/protocol/index.ts +33 -0
  365. package/src/protocol/messages.ts +168 -0
  366. package/src/protocol/schema.ts +76 -0
  367. package/src/protocol/values.ts +52 -0
  368. package/src/sql-split.ts +220 -0
  369. package/src/sql.ts +152 -0
  370. package/src/structure/StructureView.tsx +269 -0
  371. package/src/structure/structure-text.ts +4 -0
  372. package/src/table/CommandField.tsx +318 -0
  373. package/src/table/ImportDialog.tsx +168 -0
  374. package/src/table/TableStatusBar.tsx +91 -0
  375. package/src/table/TableToolbar.tsx +217 -0
  376. package/src/table/TableView.tsx +880 -0
  377. package/src/table/command-field.ts +285 -0
  378. package/src/table/grid-rows.ts +80 -0
  379. package/src/table/import-mapping.ts +37 -0
  380. package/src/table/layout-store.ts +62 -0
  381. package/src/table/paging.ts +33 -0
  382. package/src/table/pending.ts +143 -0
  383. package/src/table/references.ts +38 -0
  384. package/src/table/useLoaded.ts +58 -0
  385. package/src/table/useStoredLayout.ts +53 -0
  386. package/src/table/useTableTransfer.ts +187 -0
  387. package/src/testing/fake.ts +533 -0
  388. package/src/testing/index.ts +1 -0
  389. package/src/use-popup-press.ts +27 -0
  390. package/src/use-schema-change.ts +19 -0
  391. package/src/use-stable-callback.ts +12 -0
  392. package/src/value/ValuePanel.tsx +254 -0
  393. package/src/value/draft.ts +72 -0
  394. package/src/value/hex.ts +62 -0
  395. package/src/value/json.ts +74 -0
  396. package/src/value/views.ts +68 -0
  397. package/src/workbench/DatabaseWorkbench.tsx +348 -0
  398. package/src/workbench/DesignerTab.tsx +37 -0
  399. package/src/workbench/TableTab.tsx +27 -0
  400. package/src/workbench/tabs.ts +184 -0
@@ -0,0 +1,186 @@
1
+ import { useMemo, useRef, useState, type Ref } from 'react';
2
+ import clsx from 'clsx';
3
+ import { CircleAlert, RefreshCw } from 'lucide-react';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { Banner, Button, Spinner, messageOf } from '@adecore/ui';
6
+ import { useDatabaseAction, useDatabaseClient } from '../client-context.ts';
7
+ import type { Connection, ExecuteResult } from '../client/types.ts';
8
+ import { dialectOf, draftOf, emptyDraft, type Dialect, type TableDraft } from '../ddl/index.ts';
9
+ import type { TableStructure } from '../protocol/index.ts';
10
+ import { useLoaded } from '../table/useLoaded.ts';
11
+ import { useSchemaChange } from '../use-schema-change.ts';
12
+ import { DesignerView } from './DesignerView.tsx';
13
+ import { planOf } from './plan.ts';
14
+ import { recoverFrom } from './recover.ts';
15
+ import { useReferenceColumns } from './useReferenceColumns.ts';
16
+
17
+ export interface TableDesignerProps {
18
+ connection: Connection;
19
+ schema: string;
20
+ /* The table to modify; without one the designer makes a new table. */
21
+ table?: string;
22
+ className?: string;
23
+ ref?: Ref<HTMLDivElement>;
24
+ }
25
+
26
+ /* What the designer starts from. */
27
+ interface Snapshot {
28
+ readonly dialect: Dialect;
29
+ /* `null` for a table that does not exist yet. */
30
+ readonly structure: TableStructure | null;
31
+ readonly tables: readonly string[];
32
+ }
33
+
34
+ interface Editing {
35
+ readonly snapshot: Snapshot | null;
36
+ /* The draft Revert goes back to; the draft is this very object until something is edited. */
37
+ readonly baseline: TableDraft;
38
+ readonly draft: TableDraft;
39
+ }
40
+
41
+ /* Creates a table or modifies one: its columns, indexes, foreign keys and options, with the SQL it will run shown before it runs. */
42
+ export function TableDesigner({ connection, schema, table, className, ref }: TableDesignerProps) {
43
+ const { t } = useTranslation('database');
44
+ const client = useDatabaseClient();
45
+ const onAction = useDatabaseAction();
46
+ const [subject, setSubject] = useState({ requested: table, current: table ?? null });
47
+ const [editing, setEditing] = useState<Editing>({ snapshot: null, baseline: emptyDraft(), draft: emptyDraft() });
48
+ const [confirming, setConfirming] = useState(false);
49
+ const [failure, setFailure] = useState<string | null>(null);
50
+ const [shapeChanged, setShapeChanged] = useState(false);
51
+ const applying = useRef(false);
52
+
53
+ if (subject.requested !== table) {
54
+ setSubject({ requested: table, current: table ?? null });
55
+ }
56
+
57
+ const loaded = useLoaded<Snapshot>(
58
+ async (signal) => {
59
+ const session = client.session(connection);
60
+ const [server, structure, tables] = await Promise.all([
61
+ session.server({ signal }),
62
+ subject.current === null ? Promise.resolve(null) : session.structure(schema, subject.current, { signal }),
63
+ session.tables(schema, { signal })
64
+ ]);
65
+ return { dialect: dialectOf(server), structure, tables: tables.filter((info) => info.kind === 'table').map((info) => info.name) };
66
+ },
67
+ JSON.stringify([connection.id, schema, subject.current])
68
+ );
69
+ const snapshot = loaded.value;
70
+
71
+ // The apply of this designer reloads by itself; a change from elsewhere replaces an untouched draft and warns about an edited one.
72
+ useSchemaChange(connection.id, schema, () => {
73
+ if (applying.current) {
74
+ return;
75
+ }
76
+
77
+ if (editing.draft === editing.baseline) {
78
+ loaded.reload();
79
+ } else {
80
+ setShapeChanged(true);
81
+ }
82
+ });
83
+
84
+ // A new snapshot, after a load or an apply, starts a fresh draft.
85
+ if (snapshot !== null && editing.snapshot !== snapshot) {
86
+ const baseline = snapshot.structure === null ? emptyDraft() : draftOf(snapshot.structure);
87
+ setEditing({ snapshot, baseline, draft: baseline });
88
+ setShapeChanged(false);
89
+ }
90
+ const draft = editing.draft;
91
+
92
+ const plan = useMemo(() => (snapshot === null ? null : planOf(snapshot.dialect, schema, snapshot.structure, draft)), [snapshot, schema, draft]);
93
+ const referenceColumns = useReferenceColumns(client, connection, schema, draft);
94
+
95
+ const readOnlyReason =
96
+ connection.config.readOnly === true ? t('designer.readOnly.connection') : snapshot?.structure?.kind === 'view' ? t('designer.readOnly.view') : null;
97
+
98
+ async function apply(): Promise<void> {
99
+ if (snapshot === null || plan === null) {
100
+ return;
101
+ }
102
+ const session = client.session(connection);
103
+ const statements = plan.statements;
104
+ const wasNew = snapshot.structure === null;
105
+ let outcome: ExecuteResult;
106
+ applying.current = true;
107
+ try {
108
+ outcome = await session.execute(statements.join(';\n'), { schema });
109
+ } catch (e) {
110
+ setFailure(messageOf(e, t('designer.failed')));
111
+ setConfirming(false);
112
+ return;
113
+ } finally {
114
+ applying.current = false;
115
+ }
116
+ const failed = outcome.results.find((result) => result.kind === 'error');
117
+ if (failed?.kind === 'error') {
118
+ await recoverFrom(session, statements, failed.sql, outcome.inTransaction);
119
+ setFailure(failed.error.message);
120
+ setConfirming(false);
121
+ return;
122
+ }
123
+ setFailure(null);
124
+ setConfirming(false);
125
+ // The client has told its listeners already: `execute` does for a statement that creates, alters or drops.
126
+ if (wasNew || draft.name !== draft.originalName) {
127
+ setSubject({ requested: table, current: draft.name });
128
+ onAction?.({ kind: 'edit-table', ref: { connectionId: connection.id, schema, table: draft.name } });
129
+ } else {
130
+ loaded.reload();
131
+ }
132
+ }
133
+
134
+ return (
135
+ <div ref={ref} className={clsx('flex min-h-0 min-w-0 flex-col', className)}>
136
+ {(loaded.loading || snapshot === null) && loaded.error === null && (
137
+ <div className="grid grow place-items-center py-8 text-text-muted">
138
+ <Spinner size={20} label={t('designer.loading')} />
139
+ </div>
140
+ )}
141
+ {loaded.error !== null && snapshot === null && (
142
+ <div className="p-3">
143
+ <Banner icon={CircleAlert} tone="error" className="w-full" message={loaded.error}>
144
+ <Button variant="secondary" size="sm" onClick={loaded.reload}>
145
+ {t('designer.retry')}
146
+ </Button>
147
+ </Banner>
148
+ </div>
149
+ )}
150
+ {shapeChanged && (
151
+ <div className="shrink-0 px-3 pt-3">
152
+ <Banner icon={RefreshCw} tone="neutral" className="w-full" message={t('designer.changed')}>
153
+ <Button variant="secondary" size="sm" onClick={loaded.reload}>
154
+ {t('designer.reload')}
155
+ </Button>
156
+ </Banner>
157
+ </div>
158
+ )}
159
+ {!loaded.loading && snapshot !== null && plan !== null && (
160
+ <DesignerView
161
+ className="grow"
162
+ dialect={snapshot.dialect}
163
+ schema={schema}
164
+ draft={draft}
165
+ isNew={snapshot.structure === null}
166
+ changed={draft !== editing.baseline}
167
+ tables={snapshot.tables}
168
+ plan={plan}
169
+ readOnlyReason={readOnlyReason}
170
+ failure={failure}
171
+ confirming={confirming}
172
+ referenceColumns={referenceColumns}
173
+ onChange={(next) => setEditing({ ...editing, draft: next })}
174
+ onRequestApply={() => setConfirming(true)}
175
+ onConfirm={apply}
176
+ onCancelConfirm={() => setConfirming(false)}
177
+ onRevert={() => {
178
+ setEditing({ ...editing, draft: editing.baseline });
179
+ setFailure(null);
180
+ }}
181
+ onDismissFailure={() => setFailure(null)}
182
+ />
183
+ )}
184
+ </div>
185
+ );
186
+ }
@@ -0,0 +1,137 @@
1
+ import { emptyColumn, nextKey, type ColumnDraft, type ForeignKeyDraft, type IndexDraft, type TableDraft, type TableOptions } from '../ddl/index.ts';
2
+
3
+ /* The editing steps of a draft. Each returns a new draft and leaves the one it was given alone. */
4
+
5
+ const replaceAt = <T extends { readonly key: string }>(items: readonly T[], key: string, change: (item: T) => T): T[] =>
6
+ items.map((item) => (item.key === key ? change(item) : item));
7
+
8
+ const toggled = (names: readonly string[], name: string): string[] =>
9
+ names.includes(name) ? names.filter((candidate) => candidate !== name) : [...names, name];
10
+
11
+ export const renameTable = (draft: TableDraft, name: string): TableDraft => ({ ...draft, name });
12
+
13
+ export const addColumn = (draft: TableDraft, type: string): TableDraft => ({
14
+ ...draft,
15
+ columns: [...draft.columns, { ...emptyColumn(), type, name: `column_${draft.columns.length + 1}` }]
16
+ });
17
+
18
+ /* A rename carries along every key, index and foreign key that names the column. */
19
+ export const patchColumn = (draft: TableDraft, key: string, patch: Partial<ColumnDraft>): TableDraft => {
20
+ const before = draft.columns.find((column) => column.key === key);
21
+ if (before === undefined) {
22
+ return draft;
23
+ }
24
+ const inKey = draft.primaryKey.includes(before.name);
25
+ const applied: Partial<ColumnDraft> = inKey && patch.nullable === true ? { ...patch, nullable: false } : patch;
26
+ const after = { ...before, ...applied };
27
+ const follow = (names: readonly string[]): string[] => names.map((name) => (name === before.name ? after.name : name));
28
+ return {
29
+ ...draft,
30
+ columns: replaceAt(draft.columns, key, () => after),
31
+ primaryKey: follow(draft.primaryKey),
32
+ indexes: draft.indexes.map((index) => ({ ...index, columns: follow(index.columns) })),
33
+ foreignKeys: draft.foreignKeys.map((foreignKey) => ({ ...foreignKey, columns: follow(foreignKey.columns) }))
34
+ };
35
+ };
36
+
37
+ /* What names the column goes with it: an index keeps its other columns, a foreign key on it is dropped. */
38
+ export const removeColumn = (draft: TableDraft, key: string): TableDraft => {
39
+ const gone = draft.columns.find((column) => column.key === key);
40
+ if (gone === undefined) {
41
+ return draft;
42
+ }
43
+ return {
44
+ ...draft,
45
+ columns: draft.columns.filter((column) => column.key !== key),
46
+ primaryKey: draft.primaryKey.filter((name) => name !== gone.name),
47
+ indexes: draft.indexes
48
+ .map((index) => ({ ...index, columns: index.columns.filter((name) => name !== gone.name) }))
49
+ .filter((index) => index.columns.length > 0),
50
+ foreignKeys: draft.foreignKeys.filter((foreignKey) => !foreignKey.columns.includes(gone.name))
51
+ };
52
+ };
53
+
54
+ export const moveColumn = (draft: TableDraft, key: string, offset: -1 | 1): TableDraft => {
55
+ const from = draft.columns.findIndex((column) => column.key === key);
56
+ const to = from + offset;
57
+ if (from < 0 || to < 0 || to >= draft.columns.length) {
58
+ return draft;
59
+ }
60
+ const columns = [...draft.columns];
61
+ [columns[from], columns[to]] = [columns[to]!, columns[from]!];
62
+ return { ...draft, columns };
63
+ };
64
+
65
+ /* A column in the primary key cannot be null, so putting it there says so. */
66
+ export const togglePrimaryKey = (draft: TableDraft, key: string): TableDraft => {
67
+ const column = draft.columns.find((candidate) => candidate.key === key);
68
+ if (column === undefined) {
69
+ return draft;
70
+ }
71
+ const joining = !draft.primaryKey.includes(column.name);
72
+ const toggledKey = { ...draft, primaryKey: toggled(draft.primaryKey, column.name) };
73
+ return joining ? patchColumn(toggledKey, key, { nullable: false }) : toggledKey;
74
+ };
75
+
76
+ export const addIndex = (draft: TableDraft): TableDraft => ({
77
+ ...draft,
78
+ indexes: [
79
+ ...draft.indexes,
80
+ { key: nextKey(), originalName: null, name: `idx_${draft.name || 'table'}_${draft.indexes.length + 1}`, columns: [], unique: false }
81
+ ]
82
+ });
83
+
84
+ export const patchIndex = (draft: TableDraft, key: string, patch: Partial<IndexDraft>): TableDraft => ({
85
+ ...draft,
86
+ indexes: replaceAt(draft.indexes, key, (index) => ({ ...index, ...patch }))
87
+ });
88
+
89
+ export const toggleIndexColumn = (draft: TableDraft, key: string, column: string): TableDraft => ({
90
+ ...draft,
91
+ indexes: replaceAt(draft.indexes, key, (index) => ({ ...index, columns: toggled(index.columns, column) }))
92
+ });
93
+
94
+ export const removeIndex = (draft: TableDraft, key: string): TableDraft => ({ ...draft, indexes: draft.indexes.filter((index) => index.key !== key) });
95
+
96
+ export const addForeignKey = (draft: TableDraft, schema: string): TableDraft => ({
97
+ ...draft,
98
+ foreignKeys: [
99
+ ...draft.foreignKeys,
100
+ {
101
+ key: nextKey(),
102
+ originalName: null,
103
+ name: `fk_${draft.name || 'table'}_${draft.foreignKeys.length + 1}`,
104
+ columns: [],
105
+ referencedSchema: schema,
106
+ referencedTable: '',
107
+ referencedColumns: [],
108
+ onUpdate: null,
109
+ onDelete: null
110
+ }
111
+ ]
112
+ });
113
+
114
+ export const patchForeignKey = (draft: TableDraft, key: string, patch: Partial<ForeignKeyDraft>): TableDraft => ({
115
+ ...draft,
116
+ foreignKeys: replaceAt(draft.foreignKeys, key, (foreignKey) => ({ ...foreignKey, ...patch }))
117
+ });
118
+
119
+ /* Another table has other columns, so the old pick is forgotten. */
120
+ export const setReferencedTable = (draft: TableDraft, key: string, schema: string, table: string): TableDraft =>
121
+ patchForeignKey(draft, key, { referencedSchema: schema, referencedTable: table, referencedColumns: [] });
122
+
123
+ export const toggleForeignKeyColumn = (draft: TableDraft, key: string, side: 'columns' | 'referencedColumns', column: string): TableDraft => ({
124
+ ...draft,
125
+ foreignKeys: replaceAt(draft.foreignKeys, key, (foreignKey) => ({ ...foreignKey, [side]: toggled(foreignKey[side], column) }))
126
+ });
127
+
128
+ export const removeForeignKey = (draft: TableDraft, key: string): TableDraft => ({
129
+ ...draft,
130
+ foreignKeys: draft.foreignKeys.filter((foreignKey) => foreignKey.key !== key)
131
+ });
132
+
133
+ /* A collation belongs to a character set, so choosing another set empties it. */
134
+ export const patchOptions = (draft: TableDraft, patch: Partial<TableOptions>): TableDraft => {
135
+ const charsetChanged = patch.charset !== undefined && patch.charset !== draft.options.charset;
136
+ return { ...draft, options: { ...draft.options, ...(charsetChanged ? { collation: '' } : {}), ...patch } };
137
+ };
@@ -0,0 +1,13 @@
1
+ import type { Ref } from 'react';
2
+ import type { Dialect, TableDraft } from '../ddl/index.ts';
3
+
4
+ /* What every tab of the designer is given: the draft it edits, and a way to hand back the next one. */
5
+ export interface EditorProps {
6
+ draft: TableDraft;
7
+ dialect: Dialect;
8
+ /* Nothing can be edited, such as on a connection that is read only. */
9
+ disabled: boolean;
10
+ onChange(draft: TableDraft): void;
11
+ className?: string;
12
+ ref?: Ref<HTMLDivElement>;
13
+ }
@@ -0,0 +1,25 @@
1
+ import type { TableStructure } from '../protocol/index.ts';
2
+ import { alterTableSql, createTableSql, dropsColumns, draftOf, validateDraft, type Dialect, type DraftProblem, type TableDraft } from '../ddl/index.ts';
3
+
4
+ /* What applying a draft would do. */
5
+ export interface Plan {
6
+ readonly problems: readonly DraftProblem[];
7
+ /* Empty while there are problems, since a draft that does not hold together has no SQL. */
8
+ readonly statements: readonly string[];
9
+ /* Whether the statements remove a column and the data in it. */
10
+ readonly destructive: boolean;
11
+ }
12
+
13
+ export const planOf = (dialect: Dialect, schema: string, structure: TableStructure | null, draft: TableDraft): Plan => {
14
+ const problems = validateDraft(dialect, draft);
15
+ if (problems.length > 0) {
16
+ return { problems, statements: [], destructive: false };
17
+ }
18
+ if (structure === null) {
19
+ return { problems, statements: createTableSql(dialect, schema, draft), destructive: false };
20
+ }
21
+ return { problems, statements: alterTableSql(dialect, schema, structure, draft), destructive: dropsColumns(draftOf(structure), draft) };
22
+ };
23
+
24
+ /* The statements as a person reads them and as a script runs them. */
25
+ export const scriptOf = (statements: readonly string[]): string => statements.map((statement) => `${statement};`).join('\n');
@@ -0,0 +1,20 @@
1
+ import type { DatabaseSession } from '../client/types.ts';
2
+
3
+ const REBUILD_START = 'PRAGMA foreign_keys=OFF';
4
+
5
+ /*
6
+ * A failed rebuild leaves SQLite with a transaction it began and foreign keys it switched off.
7
+ * Only a transaction this script began is rolled back, since one the person had open is theirs.
8
+ */
9
+ export async function recoverFrom(session: DatabaseSession, statements: readonly string[], failedSql: string, inTransaction: boolean): Promise<void> {
10
+ try {
11
+ if (inTransaction && statements.includes('BEGIN') && failedSql.trim().toUpperCase() !== 'BEGIN') {
12
+ await session.transaction('rollback');
13
+ }
14
+ if (statements[0] === REBUILD_START) {
15
+ await session.execute('PRAGMA foreign_keys=ON');
16
+ }
17
+ } catch {
18
+ // The error of the statement that failed is the one worth showing.
19
+ }
20
+ }
@@ -0,0 +1,54 @@
1
+ import { useEffect, useState } from 'react';
2
+ import type { Connection, DatabaseClient } from '../client/types.ts';
3
+ import type { TableDraft } from '../ddl/index.ts';
4
+
5
+ interface Cache {
6
+ readonly scope: string;
7
+ readonly columns: Readonly<Record<string, readonly string[]>>;
8
+ }
9
+
10
+ /*
11
+ * The columns of the tables the draft's foreign keys point at, loaded as they are picked. `scope`
12
+ * is the connection and the schema, so a cache never answers for another. A table that fails to
13
+ * load has no columns to pick from, which stops the wait.
14
+ */
15
+ export function useReferenceColumns(
16
+ client: DatabaseClient,
17
+ connection: Connection,
18
+ schema: string,
19
+ draft: TableDraft
20
+ ): (table: string) => readonly string[] | null {
21
+ const scope = `${connection.id}\u0000${schema}`;
22
+ const [cache, setCache] = useState<Cache>({ scope, columns: {} });
23
+ const known = cache.scope === scope ? cache.columns : {};
24
+ const wanted = [...new Set(draft.foreignKeys.map((foreignKey) => foreignKey.referencedTable))]
25
+ .filter((table) => table !== '' && table !== draft.name && !(table in known))
26
+ .sort();
27
+ const wantedKey = wanted.join('\u0000');
28
+
29
+ useEffect(() => {
30
+ if (wantedKey === '') {
31
+ return undefined;
32
+ }
33
+ let current = true;
34
+ const session = client.session(connection);
35
+ for (const table of wantedKey.split('\u0000')) {
36
+ session
37
+ .structure(schema, table)
38
+ .then(
39
+ (structure) => structure.columns.map((column) => column.name),
40
+ () => []
41
+ )
42
+ .then((names) => {
43
+ if (current) {
44
+ setCache((now) => ({ scope, columns: { ...(now.scope === scope ? now.columns : {}), [table]: names } }));
45
+ }
46
+ });
47
+ }
48
+ return () => {
49
+ current = false;
50
+ };
51
+ }, [client, connection, schema, scope, wantedKey]);
52
+
53
+ return (table) => (table === draft.name ? draft.columns.map((column) => column.name) : (known[table] ?? null));
54
+ }