@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
package/dist/sql.js ADDED
@@ -0,0 +1,104 @@
1
+ const NUMERIC_KINDS = new Set(['integer', 'decimal', 'float']);
2
+ const NUMERIC_TEXT = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/;
3
+ const ORDER_ITEM = /\s*(`(?:[^`]|``)+`|"(?:[^"]|"")+"|[A-Za-z_][\w$]*)(?:\s+(asc|desc))?\s*(,|$)/iy;
4
+ const markOf = (engine) => (engine === 'mysql' ? '`' : '"');
5
+ export const quoteIdentifier = (engine, name) => {
6
+ const mark = markOf(engine);
7
+ return `${mark}${name.replaceAll(mark, mark + mark)}${mark}`;
8
+ };
9
+ /* `schema.table` with both parts quoted, or the table alone. */
10
+ export const qualifiedName = (target) => target.schema === undefined || target.schema === ''
11
+ ? quoteIdentifier(target.engine, target.table)
12
+ : `${quoteIdentifier(target.engine, target.schema)}.${quoteIdentifier(target.engine, target.table)}`;
13
+ const stringLiteral = (engine, text) => {
14
+ // MySQL reads a backslash as an escape unless the server runs with NO_BACKSLASH_ESCAPES.
15
+ const escaped = engine === 'mysql' ? text.replaceAll('\\', '\\\\') : text;
16
+ return `'${escaped.replaceAll("'", "''")}'`;
17
+ };
18
+ const binaryLiteral = (engine, hex) => (engine === 'mysql' && hex !== '' ? `0x${hex}` : `X'${hex}'`);
19
+ /* A value as SQL source. `kind` lets a number that arrived as text (a decimal, a big integer) stay a number. */
20
+ export const sqlLiteral = (engine, value, kind) => {
21
+ if (value === null) {
22
+ return 'NULL';
23
+ }
24
+ switch (typeof value) {
25
+ case 'boolean':
26
+ return engine === 'mysql' ? (value ? 'TRUE' : 'FALSE') : value ? '1' : '0';
27
+ case 'number':
28
+ return Number.isFinite(value) ? String(value) : 'NULL';
29
+ case 'string': {
30
+ const trimmed = value.trim();
31
+ return kind !== undefined && NUMERIC_KINDS.has(kind) && NUMERIC_TEXT.test(trimmed) ? trimmed : stringLiteral(engine, value);
32
+ }
33
+ }
34
+ switch (value.kind) {
35
+ case 'default':
36
+ return 'DEFAULT';
37
+ case 'binary':
38
+ return binaryLiteral(engine, value.hex);
39
+ case 'longText':
40
+ return stringLiteral(engine, value.preview);
41
+ }
42
+ };
43
+ /* `col = literal`, or `col IS NULL` since `= NULL` never matches. */
44
+ export const equalsCondition = (engine, column, value, kind) => value === null ? `${quoteIdentifier(engine, column)} IS NULL` : `${quoteIdentifier(engine, column)} = ${sqlLiteral(engine, value, kind)}`;
45
+ /* `col <> literal`, or `col IS NOT NULL` since `<> NULL` never matches. */
46
+ export const notEqualsCondition = (engine, column, value, kind) => value === null ? `${quoteIdentifier(engine, column)} IS NOT NULL` : `${quoteIdentifier(engine, column)} <> ${sqlLiteral(engine, value, kind)}`;
47
+ /* `a = 1 AND b IS NULL` for the columns of a key; the text a WHERE field holds. */
48
+ export const matchCondition = (engine, matches) => matches.map((match) => equalsCondition(engine, match.column, match.value, match.kind)).join(' AND ');
49
+ /* The text after `ORDER BY`: every column quoted, every direction spelled out. */
50
+ export const orderByClause = (engine, sorts) => sorts.map((sort) => `${quoteIdentifier(engine, sort.column)} ${sort.direction === 'asc' ? 'ASC' : 'DESC'}`).join(', ');
51
+ const unquote = (engine, word) => {
52
+ const mark = word[0];
53
+ if (mark !== '`' && mark !== '"') {
54
+ return word;
55
+ }
56
+ // A double quoted word is a string in MySQL, which sorts by a constant and so is not a column.
57
+ if (mark === '"' && engine === 'mysql') {
58
+ return null;
59
+ }
60
+ return word.slice(1, -1).replaceAll(mark + mark, mark);
61
+ };
62
+ const readOrderBy = (engine, text, resolve) => {
63
+ const source = text.trim();
64
+ if (source === '') {
65
+ return [];
66
+ }
67
+ const sorts = [];
68
+ let position = 0;
69
+ while (position < source.length) {
70
+ ORDER_ITEM.lastIndex = position;
71
+ const match = ORDER_ITEM.exec(source);
72
+ if (match === null) {
73
+ return null;
74
+ }
75
+ const word = unquote(engine, match[1]);
76
+ const column = word === null ? undefined : resolve(word);
77
+ if (column === undefined || sorts.some((sort) => sort.column === column)) {
78
+ return null;
79
+ }
80
+ sorts.push({ column, direction: match[2]?.toLowerCase() === 'desc' ? 'desc' : 'asc' });
81
+ position = ORDER_ITEM.lastIndex;
82
+ if (match[3] === '') {
83
+ return sorts;
84
+ }
85
+ if (position >= source.length) {
86
+ return null;
87
+ }
88
+ }
89
+ return sorts;
90
+ };
91
+ const matchColumn = (columns, word) => columns.find((name) => name === word) ?? columns.find((name) => name.toLowerCase() === word.toLowerCase());
92
+ /*
93
+ * The sorts an ORDER BY text says, or `null` when it is anything but a plain list of the given
94
+ * columns, each with an optional direction. Empty text is an empty list. A column is found without
95
+ * regard to case, since both engines treat names that way.
96
+ */
97
+ export const parseOrderBy = (engine, text, columns) => readOrderBy(engine, text, (word) => matchColumn(columns, word));
98
+ /* The same for a text read before the columns are known, so a column keeps the spelling the text gave it. */
99
+ export const parseOrderByLoose = (engine, text) => readOrderBy(engine, text, (word) => word);
100
+ /* The column a word names, which may be quoted the way the engine quotes, or `undefined` when none of the columns is meant. */
101
+ export const findColumn = (engine, word, columns) => {
102
+ const bare = unquote(engine, word);
103
+ return bare === null ? undefined : matchColumn(columns, bare);
104
+ };
@@ -0,0 +1,14 @@
1
+ import { type Ref } from 'react';
2
+ import type { Connection } from '../client/types.ts';
3
+ import type { TableStructure } from '../protocol/index.ts';
4
+ export interface StructureViewProps {
5
+ connection: Connection;
6
+ schema: string;
7
+ table: string;
8
+ className?: string;
9
+ ref?: Ref<HTMLDivElement>;
10
+ }
11
+ export declare function StructureView({ connection, schema, table, className, ref }: StructureViewProps): import("react").JSX.Element;
12
+ export declare function StructureTabs({ structure }: {
13
+ structure: TableStructure;
14
+ }): import("react").JSX.Element;
@@ -0,0 +1,99 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useState } from 'react';
3
+ import clsx from 'clsx';
4
+ import { CircleAlert, Copy, KeyRound, Link2 } from 'lucide-react';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { Banner, Button, EmptyState, Icon, Pill, Spinner, Tabs, copyText, messageOf } from '@adecore/ui';
7
+ import { useDatabaseClient } from '../client-context.js';
8
+ import { useSchemaChange } from '../use-schema-change.js';
9
+ import { referenceOf } from './structure-text.js';
10
+ const TH = 'sticky top-0 h-7 bg-surface px-3 text-left text-xs font-medium whitespace-nowrap text-text-faint';
11
+ const TD = 'h-7 border-t border-border-soft px-3 text-xs whitespace-nowrap text-text select-text';
12
+ const MONO = 'font-mono text-code';
13
+ /* The columns, indexes, foreign keys and DDL of one table or view. */
14
+ export function StructureView({ connection, schema, table, className, ref }) {
15
+ const { t } = useTranslation('database');
16
+ const client = useDatabaseClient();
17
+ const [answer, setAnswer] = useState(null);
18
+ const [attempt, setAttempt] = useState(0);
19
+ // A schema change reloads without leaving the structure on screen: the answer keeps its place until the new one arrives.
20
+ const [reloads, setReloads] = useState(0);
21
+ const answered = answer !== null && answer.connection === connection && answer.schema === schema && answer.table === table && answer.attempt === attempt;
22
+ const load = answered ? answer.load : { status: 'loading' };
23
+ useSchemaChange(connection.id, schema, () => setReloads((now) => now + 1));
24
+ useEffect(() => {
25
+ let current = true;
26
+ const settle = (outcome) => {
27
+ if (current) {
28
+ setAnswer({ connection, schema, table, attempt, load: outcome });
29
+ }
30
+ };
31
+ client
32
+ .session(connection)
33
+ .structure(schema, table)
34
+ .then((structure) => settle({ status: 'ready', structure }), (e) => settle({ status: 'error', message: messageOf(e, t('structure.failed')) }));
35
+ return () => {
36
+ current = false;
37
+ };
38
+ }, [client, connection, schema, table, attempt, reloads, t]);
39
+ return (_jsxs("div", { ref: ref, className: clsx('flex min-h-0 min-w-0 flex-col', className), children: [load.status === 'loading' && (_jsx("div", { className: "grid grow place-items-center py-8 text-text-muted", children: _jsx(Spinner, { size: 20, label: t('structure.loading') }) })), load.status === 'error' && (_jsx("div", { className: "p-3", children: _jsx(Banner, { icon: CircleAlert, tone: "error", className: "w-full", message: load.message, children: _jsx(Button, { variant: "secondary", size: "sm", onClick: () => setAttempt(attempt + 1), children: t('structure.retry') }) }) })), load.status === 'ready' && _jsx(StructureTabs, { structure: load.structure })] }));
40
+ }
41
+ export function StructureTabs({ structure }) {
42
+ const { t } = useTranslation('database');
43
+ return (_jsxs(Tabs.Root, { defaultValue: "columns", className: "flex min-h-0 grow flex-col", children: [_jsxs(Tabs.List, { className: "shrink-0 px-3", children: [_jsxs(Tabs.Tab, { value: "columns", children: [t('structure.columns'), _jsx(Tabs.Count, { value: structure.columns.length })] }), _jsxs(Tabs.Tab, { value: "indexes", children: [t('structure.indexes'), _jsx(Tabs.Count, { value: structure.indexes.length })] }), _jsxs(Tabs.Tab, { value: "foreignKeys", children: [t('structure.foreignKeys'), _jsx(Tabs.Count, { value: structure.foreignKeys.length })] }), _jsx(Tabs.Tab, { value: "ddl", children: t('structure.ddl') })] }), _jsx(Tabs.Panel, { value: "columns", className: "min-h-0 grow overflow-auto", children: _jsx(ColumnsTable, { structure: structure }) }), _jsx(Tabs.Panel, { value: "indexes", className: "min-h-0 grow overflow-auto", children: _jsx(IndexesTable, { indexes: structure.indexes }) }), _jsx(Tabs.Panel, { value: "foreignKeys", className: "min-h-0 grow overflow-auto", children: _jsx(ForeignKeysTable, { foreignKeys: structure.foreignKeys }) }), _jsx(Tabs.Panel, { value: "ddl", className: "min-h-0 grow overflow-auto", children: _jsx(Ddl, { ddl: structure.ddl }) })] }));
44
+ }
45
+ function Grid({ heads, children }) {
46
+ return (_jsxs("table", { className: "w-full border-collapse", children: [_jsx("thead", { children: _jsx("tr", { children: heads.map((head) => (_jsx("th", { scope: "col", className: TH, children: head }, head))) }) }), _jsx("tbody", { children: children })] }));
47
+ }
48
+ function YesNo({ value }) {
49
+ const { t } = useTranslation('database');
50
+ return _jsx(_Fragment, { children: value ? t('structure.yes') : t('structure.no') });
51
+ }
52
+ function ColumnsTable({ structure }) {
53
+ const { t } = useTranslation('database');
54
+ const heads = [
55
+ t('structure.column.name'),
56
+ t('structure.column.type'),
57
+ t('structure.column.nullable'),
58
+ t('structure.column.default'),
59
+ t('structure.column.extra'),
60
+ t('structure.column.comment')
61
+ ];
62
+ const foreign = new Set(structure.foreignKeys.flatMap((key) => key.columns));
63
+ if (structure.columns.length === 0) {
64
+ return _jsx(EmptyState, { children: t('structure.noColumns') });
65
+ }
66
+ return (_jsx(Grid, { heads: heads, children: structure.columns.map((column) => (_jsx(ColumnRow, { column: column, primary: structure.primaryKey.includes(column.name), foreign: foreign.has(column.name) }, column.name))) }));
67
+ }
68
+ function ColumnRow({ column, primary, foreign }) {
69
+ const { t } = useTranslation('database');
70
+ return (_jsxs("tr", { children: [_jsx("td", { className: TD, children: _jsxs("span", { className: "flex items-center gap-1.5", children: [primary ? (_jsx(Icon, { icon: KeyRound, size: 12, className: "shrink-0 text-status-needs-you" })) : foreign ? (_jsx(Icon, { icon: Link2, size: 12, className: "shrink-0 text-text-muted" })) : (_jsx("span", { className: "size-3 shrink-0" })), column.name] }) }), _jsx("td", { className: clsx(TD, MONO), children: column.type }), _jsx("td", { className: clsx(TD, 'text-text-muted'), children: _jsx(YesNo, { value: column.nullable }) }), _jsx("td", { className: clsx(TD, MONO), children: column.defaultValue }), _jsx("td", { className: TD, children: _jsxs("span", { className: "flex gap-1", children: [column.autoIncrement && _jsx(Pill, { children: t('structure.autoIncrement') }), column.generated && _jsx(Pill, { children: t('structure.generated') })] }) }), _jsx("td", { className: clsx(TD, 'text-text-muted'), children: column.comment })] }));
71
+ }
72
+ function IndexesTable({ indexes }) {
73
+ const { t } = useTranslation('database');
74
+ if (indexes.length === 0) {
75
+ return _jsx(EmptyState, { children: t('structure.noIndexes') });
76
+ }
77
+ return (_jsx(Grid, { heads: [t('structure.index.name'), t('structure.index.columns'), t('structure.index.kind')], children: indexes.map((index) => (_jsxs("tr", { children: [_jsx("td", { className: TD, children: index.name }), _jsx("td", { className: clsx(TD, MONO), children: index.columns.join(', ') }), _jsx("td", { className: TD, children: _jsxs("span", { className: "flex gap-1", children: [index.primary && _jsx(Pill, { tone: "accent", children: t('structure.primary') }), index.unique && !index.primary && _jsx(Pill, { children: t('structure.unique') })] }) })] }, index.name))) }));
78
+ }
79
+ function ForeignKeysTable({ foreignKeys }) {
80
+ const { t } = useTranslation('database');
81
+ if (foreignKeys.length === 0) {
82
+ return _jsx(EmptyState, { children: t('structure.noForeignKeys') });
83
+ }
84
+ const action = (value) => value ?? t('structure.engineDefault');
85
+ return (_jsx(Grid, { heads: [
86
+ t('structure.foreignKey.name'),
87
+ t('structure.foreignKey.columns'),
88
+ t('structure.foreignKey.references'),
89
+ t('structure.foreignKey.onUpdate'),
90
+ t('structure.foreignKey.onDelete')
91
+ ], children: foreignKeys.map((key, i) => (_jsxs("tr", { children: [_jsx("td", { className: TD, children: key.name }), _jsx("td", { className: clsx(TD, MONO), children: key.columns.join(', ') }), _jsx("td", { className: clsx(TD, MONO), children: referenceOf(key) }), _jsx("td", { className: clsx(TD, 'text-text-muted'), children: action(key.onUpdate) }), _jsx("td", { className: clsx(TD, 'text-text-muted'), children: action(key.onDelete) })] }, key.name ?? i))) }));
92
+ }
93
+ function Ddl({ ddl }) {
94
+ const { t } = useTranslation('database');
95
+ if (ddl === null) {
96
+ return _jsx(EmptyState, { children: t('structure.noDdl') });
97
+ }
98
+ return (_jsxs("div", { className: "relative", children: [_jsxs(Button, { variant: "secondary", size: "xs", className: "absolute top-2 right-3", onClick: () => copyText(ddl), children: [_jsx(Icon, { icon: Copy, size: 12 }), t('structure.copy')] }), _jsx("pre", { className: clsx(MONO, 'overflow-auto p-3 pr-24 whitespace-pre text-text select-text'), children: ddl })] }));
99
+ }
@@ -0,0 +1,2 @@
1
+ import type { ForeignKeyInfo } from '../protocol/index.ts';
2
+ export declare const referenceOf: (key: ForeignKeyInfo) => string;
@@ -0,0 +1,2 @@
1
+ /* The target of a foreign key as SQL writes it: `schema.table(column, column)`. */
2
+ export const referenceOf = (key) => `${key.referencedSchema}.${key.referencedTable}(${key.referencedColumns.join(', ')})`;
@@ -0,0 +1,14 @@
1
+ import { type Ref } from 'react';
2
+ import type { Engine } from '../protocol/index.ts';
3
+ import { type Chip, type CommandColumn } from './command-field.ts';
4
+ export interface CommandFieldProps {
5
+ chips: readonly Chip[];
6
+ columns: readonly CommandColumn[];
7
+ engine: Engine;
8
+ onChipsChange(next: Chip[]): void;
9
+ onJumpToColumn(name: string): void;
10
+ inputRef?: Ref<HTMLInputElement>;
11
+ className?: string;
12
+ ref?: Ref<HTMLDivElement>;
13
+ }
14
+ export declare function CommandField({ chips, columns, engine, onChipsChange, onJumpToColumn, inputRef, className, ref }: CommandFieldProps): import("react").JSX.Element;
@@ -0,0 +1,157 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useId, useImperativeHandle, useMemo, useRef, useState } from 'react';
3
+ import clsx from 'clsx';
4
+ import { ArrowDown, ArrowDownWideNarrow, ArrowRightToLine, ArrowUp, Code, Filter, Search, X } from 'lucide-react';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { Icon, IconButton, Kbd, KEY_SHORTCUTS, Menu, shortcut } from '@adecore/ui';
7
+ import { addChip, filterStartText, removeChipAt, replaceChipAt, suggest, toggleSort } from './command-field.js';
8
+ const FOCUS_SHORTCUT = shortcut('Mod+F');
9
+ const SUGGESTION_ICON = {
10
+ condition: Filter,
11
+ filter: Filter,
12
+ sort: ArrowDownWideNarrow,
13
+ jump: ArrowRightToLine,
14
+ sql: Code
15
+ };
16
+ const columnOf = (suggestion) => (suggestion.kind === 'sql' || suggestion.kind === 'condition' ? null : suggestion.column);
17
+ /*
18
+ * One field for the conditions and the sorts of a table. They are chips in front of the text; typing
19
+ * opens suggestions to filter on a column, sort by it or jump to it, and a text that reads as a
20
+ * condition (`quantity > 1`) becomes a chip on Enter.
21
+ */
22
+ export function CommandField({ chips, columns, engine, onChipsChange, onJumpToColumn, inputRef, className, ref }) {
23
+ const { t } = useTranslation('database');
24
+ const listId = useId();
25
+ const input = useRef(null);
26
+ const [text, setText] = useState('');
27
+ /* The chip whose text is in the input to be changed; it stays in the list, hidden, until the change is made. */
28
+ const [editing, setEditing] = useState(null);
29
+ const [focused, setFocused] = useState(false);
30
+ const [active, setActive] = useState(0);
31
+ const [dismissed, setDismissed] = useState(false);
32
+ const suggestions = useMemo(() => suggest(engine, text, columns), [engine, text, columns]);
33
+ const open = focused && !dismissed && suggestions.length > 0;
34
+ const optionId = (index) => `${listId}-${index}`;
35
+ useImperativeHandle(inputRef, () => input.current, []);
36
+ const change = (value) => {
37
+ setText(value);
38
+ setActive(0);
39
+ setDismissed(false);
40
+ };
41
+ const clear = () => {
42
+ setText('');
43
+ setEditing(null);
44
+ setActive(0);
45
+ setDismissed(false);
46
+ };
47
+ const commit = (chip) => {
48
+ onChipsChange(editing === null ? addChip(chips, chip) : replaceChipAt(chips, editing, chip));
49
+ clear();
50
+ };
51
+ const pick = (suggestion) => {
52
+ switch (suggestion.kind) {
53
+ case 'condition':
54
+ case 'sql':
55
+ commit(suggestion.chip);
56
+ break;
57
+ case 'filter':
58
+ change(filterStartText(suggestion.column));
59
+ input.current?.focus();
60
+ break;
61
+ case 'sort':
62
+ onChipsChange(toggleSort(chips, suggestion.column.name));
63
+ clear();
64
+ break;
65
+ case 'jump':
66
+ clear();
67
+ onJumpToColumn(suggestion.column.name);
68
+ break;
69
+ }
70
+ };
71
+ const edit = (index, chip) => {
72
+ setEditing(index);
73
+ setText(chip.text);
74
+ setActive(0);
75
+ setDismissed(true);
76
+ input.current?.focus();
77
+ };
78
+ const activate = (index, chip) => {
79
+ if (chip.kind === 'filter') {
80
+ edit(index, chip);
81
+ }
82
+ else if (chip.kind === 'sort') {
83
+ onChipsChange(toggleSort(chips, chip.column));
84
+ }
85
+ };
86
+ const handleKeyDown = (event) => {
87
+ if (event.nativeEvent.isComposing) {
88
+ return;
89
+ }
90
+ if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
91
+ if (suggestions.length > 0) {
92
+ event.preventDefault();
93
+ setDismissed(false);
94
+ setActive((now) => (now + (event.key === 'ArrowDown' ? 1 : -1) + suggestions.length) % suggestions.length);
95
+ }
96
+ }
97
+ else if (event.key === 'Enter') {
98
+ event.preventDefault();
99
+ const suggestion = suggestions[Math.min(active, suggestions.length - 1)];
100
+ if (suggestion !== undefined) {
101
+ pick(suggestion);
102
+ }
103
+ else if (editing !== null) {
104
+ onChipsChange(removeChipAt(chips, editing));
105
+ clear();
106
+ }
107
+ }
108
+ else if (event.key === 'Escape') {
109
+ if (open) {
110
+ event.preventDefault();
111
+ setDismissed(true);
112
+ }
113
+ else if (text !== '' || editing !== null) {
114
+ event.preventDefault();
115
+ clear();
116
+ }
117
+ }
118
+ else if (event.key === 'Backspace' && text === '' && editing === null && chips.length > 0) {
119
+ event.preventDefault();
120
+ onChipsChange(removeChipAt(chips, chips.length - 1));
121
+ }
122
+ };
123
+ const handleBlur = () => {
124
+ setFocused(false);
125
+ if (editing !== null) {
126
+ clear();
127
+ }
128
+ };
129
+ return (_jsxs("div", { ref: ref, className: clsx('field field-sm focus-ring-within relative flex h-auto min-h-7 min-w-0 flex-1 flex-wrap items-center gap-1 py-0.5', className), onClick: (event) => event.target === event.currentTarget && input.current?.focus(), children: [_jsx(Icon, { icon: Search, size: 14, className: "shrink-0 text-text-faint" }), chips.map((chip, index) => index === editing ? null : (_jsx(ChipView, { chip: chip, onActivate: () => activate(index, chip), onRemove: () => onChipsChange(removeChipAt(chips, index)) }, `${chip.kind}:${chip.kind === 'sort' ? chip.column : chip.text}`))), _jsx("input", { ref: input, role: "combobox", "aria-label": t('table.command.label'), "aria-expanded": open, "aria-controls": open ? listId : undefined, "aria-activedescendant": open ? optionId(active) : undefined, "aria-autocomplete": "list", value: text, placeholder: chips.length === 0 ? t('table.command.placeholder') : undefined, spellCheck: false, autoComplete: "off", className: "min-w-24 flex-1 bg-transparent font-mono text-code text-text outline-none placeholder:font-sans placeholder:text-text-faint", onChange: (event) => change(event.target.value), onKeyDown: handleKeyDown, onFocus: () => setFocused(true), onBlur: handleBlur }), _jsx(Kbd, { shortcut: FOCUS_SHORTCUT, variant: "inline", className: "shrink-0" }), open && (_jsx("div", { id: listId, role: "listbox", "aria-label": t('table.command.suggestions'), className: "menu-popup absolute top-full left-0 z-(--z-popup) mt-1.5 max-h-80 w-full max-w-xl",
130
+ // The input keeps the focus, or the popup would close under the pointer.
131
+ onPointerDown: (event) => event.preventDefault(), children: suggestions.map((suggestion, index) => {
132
+ const column = columnOf(suggestion);
133
+ const previous = suggestions[index - 1];
134
+ const startsGroup = column !== null && (previous === undefined || columnOf(previous)?.name !== column.name);
135
+ return (_jsxs("div", { role: "presentation", children: [startsGroup && (_jsxs(Menu.Label, { children: [_jsx("span", { className: "font-mono", children: column.name }), column.type !== '' && _jsxs("span", { children: [" \u00B7 ", column.type] })] })), suggestion.kind === 'sql' && index > 0 && _jsx(Menu.Separator, {}), _jsxs("div", { id: optionId(index), role: "option", "aria-selected": index === active, "data-active": index === active, className: "menu-item cursor-row", onPointerMove: () => setActive(index), onClick: () => pick(suggestion), children: [_jsx(Icon, { icon: SUGGESTION_ICON[suggestion.kind], size: 14 }), _jsx(SuggestionLabel, { suggestion: suggestion }), index === active && suggestion.kind !== 'sql' && _jsx(Kbd, { shortcut: KEY_SHORTCUTS.enter })] })] }, `${suggestion.kind}:${column?.name ?? ''}`));
136
+ }) }))] }));
137
+ }
138
+ function SuggestionLabel({ suggestion }) {
139
+ const { t } = useTranslation('database');
140
+ switch (suggestion.kind) {
141
+ case 'condition':
142
+ return (_jsxs(_Fragment, { children: [t('table.command.addFilter'), " ", _jsx("span", { className: "min-w-0 truncate font-mono", children: suggestion.chip.text })] }));
143
+ case 'filter':
144
+ return (_jsxs(_Fragment, { children: [t('table.command.filterOn'), " ", _jsx("span", { className: "font-mono", children: suggestion.column.name })] }));
145
+ case 'sort':
146
+ return (_jsxs(_Fragment, { children: [t('table.command.sortBy'), " ", _jsx("span", { className: "font-mono", children: suggestion.column.name })] }));
147
+ case 'jump':
148
+ return t('table.command.jumpTo');
149
+ case 'sql':
150
+ return (_jsxs(_Fragment, { children: [t('table.command.asSql'), _jsx(Menu.Hint, { className: "font-mono", children: "WHERE \u2026" })] }));
151
+ }
152
+ }
153
+ /* A filter opens its text for editing, a sort flips its direction, and a raw ORDER BY is only shown. */
154
+ function ChipView({ chip, onActivate, onRemove }) {
155
+ const { t } = useTranslation('database');
156
+ return (_jsxs("span", { className: "inline-flex h-5 max-w-72 min-w-0 shrink-0 items-center rounded-md bg-text/10 font-mono text-code text-text", children: [chip.kind === 'filter' && (_jsx("button", { type: "button", "aria-label": t('table.command.editFilter', { filter: chip.text }), className: "min-w-0 truncate pl-1.5 text-left", onClick: onActivate, children: chip.text })), chip.kind === 'sort' && (_jsxs("button", { type: "button", "aria-label": t('table.command.flipSort', { column: chip.column }), className: "inline-flex min-w-0 items-center gap-1 pl-1.5 text-left", onClick: onActivate, children: [_jsx(Icon, { icon: chip.direction === 'asc' ? ArrowUp : ArrowDown, size: 12, className: "shrink-0" }), _jsx("span", { className: "truncate", children: chip.column })] })), chip.kind === 'order' && _jsx("span", { className: "min-w-0 truncate pl-1.5", children: chip.text }), _jsx(IconButton, { icon: X, size: "2xs", label: t(chip.kind === 'sort' ? 'table.command.removeSort' : 'table.command.removeFilter'), tooltip: false, className: "shrink-0", onClick: onRemove })] }));
157
+ }
@@ -0,0 +1,27 @@
1
+ import type { Ref } from 'react';
2
+ import type { ColumnInfo } from '../protocol/index.ts';
3
+ export interface ImportSample {
4
+ readonly columns: readonly string[];
5
+ readonly rows: readonly (readonly string[])[];
6
+ }
7
+ export interface ImportFormProps {
8
+ path: string;
9
+ tableColumns: readonly ColumnInfo[];
10
+ sample: ImportSample | null;
11
+ header: boolean;
12
+ mapping: readonly (string | null)[];
13
+ busy: boolean;
14
+ error: string | null;
15
+ onHeaderChange(header: boolean): void;
16
+ onMappingChange(fileColumn: number, tableColumn: string | null): void;
17
+ onImport(): void;
18
+ onCancel(): void;
19
+ className?: string;
20
+ ref?: Ref<HTMLDivElement>;
21
+ }
22
+ export declare function ImportForm({ path, tableColumns, sample, header, mapping, busy, error, onHeaderChange, onMappingChange, onImport, onCancel, className, ref }: ImportFormProps): import("react").JSX.Element;
23
+ export interface ImportDialogProps extends ImportFormProps {
24
+ open: boolean;
25
+ onOpenChange(open: boolean): void;
26
+ }
27
+ export declare function ImportDialog({ open, onOpenChange, ...form }: ImportDialogProps): import("react").JSX.Element;
@@ -0,0 +1,28 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import clsx from 'clsx';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { Button, Dialog, FormError, Select, Spinner, Switch } from '@adecore/ui';
5
+ import { formatNumber } from '@adecore/ui/format';
6
+ import { hasMapping } from './import-mapping.js';
7
+ /* How many of the sampled rows the preview shows. */
8
+ const PREVIEW_ROWS = 8;
9
+ const SKIP = '-1';
10
+ /* The body of the import dialog: how the file's columns map onto the table's, and a look at the rows. */
11
+ export function ImportForm({ path, tableColumns, sample, header, mapping, busy, error, onHeaderChange, onMappingChange, onImport, onCancel, className, ref }) {
12
+ const { t } = useTranslation('database');
13
+ const items = [{ value: SKIP, label: t('table.import.skip') }, ...tableColumns.map((column, index) => ({ value: String(index), label: column.name }))];
14
+ const valueOf = (name) => {
15
+ const index = name === null ? -1 : tableColumns.findIndex((column) => column.name === name);
16
+ return index < 0 ? SKIP : String(index);
17
+ };
18
+ const canImport = sample !== null && hasMapping(mapping) && !busy;
19
+ return (_jsxs("div", { ref: ref, className: clsx('flex min-h-0 flex-col gap-4', className), children: [_jsx("p", { className: "truncate font-mono text-code text-text-muted", children: path }), _jsxs("div", { className: "flex items-center gap-3 text-sm text-text", children: [_jsx(Switch, { checked: header, label: t('table.import.header'), disabled: busy, onCheckedChange: onHeaderChange }), _jsx("span", { children: t('table.import.header') }), sample === null && _jsx(Spinner, { size: 12, label: t('table.import.sampling') })] }), sample !== null && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "grid min-h-0 grid-cols-2 items-center gap-x-3 gap-y-1.5 overflow-auto", children: [_jsx("span", { className: "text-xs text-text-faint", children: t('table.import.fileColumn') }), _jsx("span", { className: "text-xs text-text-faint", children: t('table.import.tableColumn') }), sample.columns.map((name, index) => (_jsx(MappingRow, { name: name, value: valueOf(mapping[index] ?? null), items: items, disabled: busy, label: t('table.import.mapTo', { column: name }), onChange: (value) => onMappingChange(index, value === SKIP ? null : (tableColumns[Number(value)]?.name ?? null)) }, index)))] }), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("span", { className: "text-xs text-text-faint", children: t('table.import.preview', { count: formatNumber(Math.min(sample.rows.length, PREVIEW_ROWS)) }) }), _jsx("div", { className: "max-h-48 overflow-auto rounded-lg border border-border", children: _jsxs("table", { className: "w-full border-collapse text-left font-mono text-code", children: [_jsx("thead", { className: "sticky top-0 bg-surface-raised", children: _jsx("tr", { children: sample.columns.map((name, index) => (_jsx("th", { className: "max-w-48 truncate border-b border-border px-2 py-1 font-medium text-text-muted", children: name }, index))) }) }), _jsx("tbody", { children: sample.rows.slice(0, PREVIEW_ROWS).map((row, rowIndex) => (_jsx("tr", { className: "border-b border-border-soft last:border-b-0", children: sample.columns.map((_, index) => (_jsx("td", { className: "max-w-48 truncate px-2 py-1 whitespace-nowrap text-text", children: row[index] ?? '' }, index))) }, rowIndex))) })] }) })] })] })), error !== null && _jsx(FormError, { children: error }), _jsxs(Dialog.Footer, { className: "mt-0", children: [_jsx(Button, { disabled: busy, onClick: onCancel, children: t('table.import.cancel') }), _jsxs(Button, { variant: "primary", disabled: !canImport, onClick: onImport, children: [busy && _jsx(Spinner, { size: 12 }), t('table.import.confirm')] })] })] }));
20
+ }
21
+ function MappingRow({ name, value, items, disabled, label, onChange }) {
22
+ return (_jsxs(_Fragment, { children: [_jsx("span", { className: "truncate font-mono text-code text-text", children: name }), _jsx(Select, { size: "sm", className: "w-full", label: label, value: value, items: items, disabled: disabled, onValueChange: onChange })] }));
23
+ }
24
+ /* The import of a file into the table: pick how the columns map, look at the rows, confirm. */
25
+ export function ImportDialog({ open, onOpenChange, ...form }) {
26
+ const { t } = useTranslation('database');
27
+ return (_jsx(Dialog.Root, { open: open, onOpenChange: (next) => !form.busy && onOpenChange(next), children: _jsxs(Dialog.Popup, { className: "flex max-h-[calc(100vh-96px)] w-[720px] max-w-[calc(100vw-48px)] flex-col p-5", children: [_jsx(Dialog.Title, { children: t('table.import.title') }), _jsx(Dialog.Description, { className: "mt-1 mb-4", children: t('table.import.description') }), _jsx(ImportForm, { ...form })] }) }));
28
+ }
@@ -0,0 +1,19 @@
1
+ import type { Ref } from 'react';
2
+ import type { Aggregates } from '../grid/aggregates.ts';
3
+ import type { PageBounds } from './paging.ts';
4
+ export interface SelectionFigures {
5
+ readonly columns: readonly string[];
6
+ readonly aggregates: Aggregates;
7
+ }
8
+ export interface TableStatusBarProps {
9
+ elapsedMs: number;
10
+ bounds: PageBounds;
11
+ counting: boolean;
12
+ selection?: SelectionFigures | null;
13
+ onCount(): void;
14
+ onPrevious(): void;
15
+ onNext(): void;
16
+ className?: string;
17
+ ref?: Ref<HTMLElement>;
18
+ }
19
+ export declare function TableStatusBar({ elapsedMs, bounds, counting, selection, onCount, onPrevious, onNext, className, ref }: TableStatusBarProps): import("react").JSX.Element;
@@ -0,0 +1,22 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import clsx from 'clsx';
3
+ import { ChevronLeft, ChevronRight } from 'lucide-react';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { Button, IconButton, Spinner, Tooltip } from '@adecore/ui';
6
+ import { formatDecimal, formatNumber, formatRounded } from '@adecore/ui/format';
7
+ import { useNumberNotation } from '../client-context.js';
8
+ import { numeralText } from '../grid/display.js';
9
+ /* Which rows are on the page and how many there are, the way to the other pages, and the figures of the selected cells. */
10
+ export function TableStatusBar({ elapsedMs, bounds, counting, selection, onCount, onPrevious, onNext, className, ref }) {
11
+ const { t } = useTranslation('database');
12
+ return (_jsxs("footer", { ref: ref, className: clsx('flex h-9 shrink-0 items-center gap-3 border-t border-border px-3 text-xs text-text-muted', className), children: [_jsxs("span", { className: "flex items-center gap-1 tabular-nums", children: [bounds.to === 0 ? t('table.noRows') : t('table.range', { from: formatNumber(bounds.from), to: formatNumber(bounds.to) }), bounds.to > 0 &&
13
+ (bounds.exact ? (_jsx("span", { children: t('table.total', { total: formatNumber(bounds.total) }) })) : (_jsxs(_Fragment, { children: [_jsx("span", { children: t('table.of') }), _jsxs(Button, { size: "xs", "aria-label": t('table.count'), disabled: counting, onClick: onCount, children: [counting && _jsx(Spinner, { size: 12 }), t('table.atLeast', { total: formatNumber(bounds.total) })] })] })))] }), _jsxs("span", { className: "flex items-center", children: [_jsx(IconButton, { icon: ChevronLeft, size: "sm", label: t('table.previousPage'), disabled: !bounds.hasPrevious, onClick: onPrevious }), _jsx(IconButton, { icon: ChevronRight, size: "sm", label: t('table.nextPage'), disabled: !bounds.hasNext, onClick: onNext })] }), _jsx(Tooltip, { label: t('table.queryTime'), children: _jsx("span", { className: "text-text-faint tabular-nums", children: t('table.elapsed', { value: formatDecimal(elapsedMs) }) }) }), selection != null && _jsx(SelectionSummary, { selection: selection })] }));
14
+ }
15
+ function SelectionSummary({ selection }) {
16
+ const { t } = useTranslation('database');
17
+ const { columns, aggregates } = selection;
18
+ const { count, numeric } = aggregates;
19
+ const notation = useNumberNotation();
20
+ const figure = (label, value) => (_jsxs("span", { className: "shrink-0", children: [_jsx("span", { className: "text-text-faint", children: label }), " ", _jsx("span", { className: "text-text", children: value })] }));
21
+ return (_jsxs("span", { className: "ml-auto flex min-w-0 items-center gap-4 tabular-nums", "data-selection": "", children: [_jsx("span", { className: "min-w-0 truncate font-mono text-text", children: columns.length === 1 ? columns[0] : t('table.aggregates.columns', { formatted: formatNumber(columns.length) }) }), _jsx("span", { className: "shrink-0 text-text-faint", children: t('table.aggregates.cells', { count, cells: formatNumber(count) }) }), numeric !== null && (_jsxs(_Fragment, { children: [figure(t('table.aggregates.sum'), formatRounded(numeric.sum, 2)), figure(t('table.aggregates.average'), formatRounded(numeric.average, 2)), figure(t('table.aggregates.minimum'), numeralText(numeric.minimumText, notation)), figure(t('table.aggregates.maximum'), numeralText(numeric.maximumText, notation))] }))] }));
22
+ }
@@ -0,0 +1,43 @@
1
+ import type { Ref } from 'react';
2
+ import type { Engine, FileFormat } from '../protocol/index.ts';
3
+ import type { Chip, CommandColumn } from './command-field.ts';
4
+ import { type PageBounds } from './paging.ts';
5
+ export interface TransferMenu {
6
+ busy: boolean;
7
+ onExport(format: FileFormat): void;
8
+ onImport?(): void;
9
+ }
10
+ export interface TableToolbarProps {
11
+ refreshing: boolean;
12
+ readOnlyReason: string | null;
13
+ hasSelection: boolean;
14
+ canRevertSelection: boolean;
15
+ valuePanelOpen: boolean;
16
+ recordViewOpen?: boolean;
17
+ transfer?: TransferMenu;
18
+ pendingCount: number;
19
+ submitting: boolean;
20
+ engine: Engine;
21
+ chips: readonly Chip[];
22
+ columns: readonly CommandColumn[];
23
+ pageSize: number;
24
+ bounds: PageBounds;
25
+ commandRef?: Ref<HTMLInputElement>;
26
+ onChipsChange(next: Chip[]): void;
27
+ onJumpToColumn(name: string): void;
28
+ onRefresh(): void;
29
+ onAddRow(): void;
30
+ onDeleteRows(): void;
31
+ onCloneRows(): void;
32
+ onRevertRows(): void;
33
+ onToggleValuePanel(): void;
34
+ onToggleRecordView?(): void;
35
+ onPageSizeChange(size: number): void;
36
+ onFirstPage(): void;
37
+ onLastPage(): void;
38
+ onSubmit(): void;
39
+ onRevert(): void;
40
+ className?: string;
41
+ ref?: Ref<HTMLDivElement>;
42
+ }
43
+ export declare function TableToolbar({ refreshing, readOnlyReason, hasSelection, canRevertSelection, valuePanelOpen, recordViewOpen, transfer, pendingCount, submitting, engine, chips, columns, pageSize, bounds, commandRef, onChipsChange, onJumpToColumn, onRefresh, onAddRow, onDeleteRows, onCloneRows, onRevertRows, onToggleValuePanel, onToggleRecordView, onPageSizeChange, onFirstPage, onLastPage, onSubmit, onRevert, className, ref }: TableToolbarProps): import("react").JSX.Element;
@@ -0,0 +1,16 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import clsx from 'clsx';
3
+ import { ChevronsLeft, ChevronsRight, CopyPlus, Download, Ellipsis, FileInput, PanelRight, Plus, RefreshCw, Rows3, TableProperties, Trash2, Undo2 } from 'lucide-react';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { Button, Icon, IconButton, Menu } from '@adecore/ui';
6
+ import { formatNumber } from '@adecore/ui/format';
7
+ import { CommandField } from './CommandField.js';
8
+ import { PAGE_SIZES } from './paging.js';
9
+ const EXPORT_FORMATS = ['csv', 'tsv', 'json', 'sql'];
10
+ /* The command field for filtering, sorting and jumping to a column, refresh, add row, the submit of pending changes, and a menu of the rest. */
11
+ export function TableToolbar({ refreshing, readOnlyReason, hasSelection, canRevertSelection, valuePanelOpen, recordViewOpen = false, transfer, pendingCount, submitting, engine, chips, columns, pageSize, bounds, commandRef, onChipsChange, onJumpToColumn, onRefresh, onAddRow, onDeleteRows, onCloneRows, onRevertRows, onToggleValuePanel, onToggleRecordView, onPageSizeChange, onFirstPage, onLastPage, onSubmit, onRevert, className, ref }) {
12
+ const { t } = useTranslation('database');
13
+ const readOnly = readOnlyReason !== null;
14
+ const tooltipOf = (label) => (readOnly ? t('table.unavailable', { action: label, reason: readOnlyReason }) : label);
15
+ return (_jsxs("div", { ref: ref, className: clsx('flex shrink-0 items-center gap-2 border-b border-border px-3 py-2', className), children: [_jsx(CommandField, { chips: chips, columns: columns, engine: engine, inputRef: commandRef, onChipsChange: onChipsChange, onJumpToColumn: onJumpToColumn }), pendingCount > 0 && (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "primary", size: "sm", disabled: submitting, onClick: onSubmit, children: t('table.submit', { changes: formatNumber(pendingCount) }) }), _jsx(Button, { size: "sm", disabled: submitting, onClick: onRevert, children: t('table.revert') })] })), _jsx(IconButton, { icon: RefreshCw, size: "sm", label: t('table.refresh'), busy: refreshing, onClick: onRefresh }), _jsx(IconButton, { icon: Plus, size: "sm", label: t('table.addRow'), tooltip: tooltipOf(t('table.addRow')), "aria-disabled": readOnly || undefined, onClick: () => !readOnly && onAddRow() }), _jsxs(Menu.Root, { children: [_jsx(IconButton, { icon: Ellipsis, size: "sm", label: t('table.more'), render: _jsx(Menu.Trigger, {}) }), _jsxs(Menu.Popup, { align: "end", children: [_jsxs(Menu.SubmenuRoot, { children: [_jsxs(Menu.SubmenuTrigger, { children: [_jsx(Icon, { icon: Rows3, size: 14 }), t('table.pageSize')] }), _jsx(Menu.Popup, { children: _jsx(Menu.RadioGroup, { value: String(pageSize), onValueChange: (value) => onPageSizeChange(Number(value)), children: PAGE_SIZES.map((size) => (_jsx(Menu.RadioItem, { value: String(size), children: t('table.perPage', { size: formatNumber(size) }) }, size))) }) })] }), _jsxs(Menu.Item, { disabled: !bounds.hasPrevious, onClick: onFirstPage, children: [_jsx(Icon, { icon: ChevronsLeft, size: 14 }), t('table.firstPage')] }), _jsxs(Menu.Item, { disabled: !bounds.hasNext, onClick: onLastPage, children: [_jsx(Icon, { icon: ChevronsRight, size: 14 }), t('table.lastPage'), !bounds.exact && _jsx(Menu.Hint, { children: t('table.lastPageCounts') })] }), _jsx(Menu.Separator, {}), onToggleRecordView !== undefined && (_jsxs(Menu.CheckboxItem, { checked: recordViewOpen, onCheckedChange: onToggleRecordView, children: [_jsx(Icon, { icon: TableProperties, size: 14 }), t('table.recordView')] })), _jsxs(Menu.CheckboxItem, { checked: valuePanelOpen, onCheckedChange: onToggleValuePanel, children: [_jsx(Icon, { icon: PanelRight, size: 14 }), t('table.valuePanel')] }), _jsx(Menu.Separator, {}), readOnly && _jsx(Menu.Label, { children: readOnlyReason }), _jsxs(Menu.Item, { disabled: readOnly || !hasSelection, onClick: onCloneRows, children: [_jsx(Icon, { icon: CopyPlus, size: 14 }), t('table.cloneRows')] }), _jsxs(Menu.Item, { disabled: !canRevertSelection, onClick: onRevertRows, children: [_jsx(Icon, { icon: Undo2, size: 14 }), t('table.revertRows')] }), _jsxs(Menu.Item, { disabled: readOnly || !hasSelection, onClick: onDeleteRows, children: [_jsx(Icon, { icon: Trash2, size: 14 }), t('table.deleteRows')] }), transfer !== undefined && (_jsxs(_Fragment, { children: [_jsx(Menu.Separator, {}), _jsxs(Menu.SubmenuRoot, { children: [_jsxs(Menu.SubmenuTrigger, { disabled: transfer.busy, children: [_jsx(Icon, { icon: Download, size: 14 }), t('table.export.label')] }), _jsx(Menu.Popup, { children: EXPORT_FORMATS.map((format) => (_jsx(Menu.Item, { onClick: () => transfer.onExport(format), children: t('table.export.as', { format: format.toUpperCase() }) }, format))) })] }), transfer.onImport !== undefined && (_jsxs(Menu.Item, { onClick: transfer.onImport, children: [_jsx(Icon, { icon: FileInput, size: 14 }), t('table.import.menu')] }))] }))] })] })] }));
16
+ }
@@ -0,0 +1,13 @@
1
+ import { type Ref } from 'react';
2
+ import { type Connection } from '../client/types.ts';
3
+ export interface TableViewProps {
4
+ connection: Connection;
5
+ schema: string;
6
+ table: string;
7
+ defaultWhere?: string;
8
+ defaultOrderBy?: string;
9
+ onDirtyChange?(dirty: boolean): void;
10
+ className?: string;
11
+ ref?: Ref<HTMLDivElement>;
12
+ }
13
+ export declare function TableView({ connection, schema, table, defaultWhere, defaultOrderBy, onDirtyChange, className, ref }: TableViewProps): import("react").JSX.Element;