@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,39 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react';
2
+ import { EMPTY_LAYOUT, parseStoredLayout, serializeLayout } from './layout-store.js';
3
+ /* How long a change waits for the next before it is written, so a drag of a column edge is one write. */
4
+ export const STORE_DELAY_MS = 500;
5
+ /*
6
+ * The layout of one table's view in the app's storage. It is read once, on mount, and written back
7
+ * in the background: the last change is still written when the view unmounts.
8
+ */
9
+ export function useStoredLayout(storage, key) {
10
+ const [stored] = useState(() => (storage === undefined ? null : parseStoredLayout(storage.get(key))));
11
+ const latest = useRef(stored ?? EMPTY_LAYOUT);
12
+ const dirty = useRef(false);
13
+ const timer = useRef(undefined);
14
+ const target = useRef({ storage, key });
15
+ useEffect(() => {
16
+ target.current = { storage, key };
17
+ });
18
+ const write = useCallback(() => {
19
+ clearTimeout(timer.current);
20
+ timer.current = undefined;
21
+ if (dirty.current) {
22
+ dirty.current = false;
23
+ target.current.storage?.set(target.current.key, serializeLayout(latest.current));
24
+ }
25
+ }, []);
26
+ useEffect(() => write, [write]);
27
+ return {
28
+ stored,
29
+ remember: (change) => {
30
+ if (storage === undefined) {
31
+ return;
32
+ }
33
+ latest.current = { ...latest.current, ...change };
34
+ dirty.current = true;
35
+ clearTimeout(timer.current);
36
+ timer.current = setTimeout(write, STORE_DELAY_MS);
37
+ }
38
+ };
39
+ }
@@ -0,0 +1,51 @@
1
+ import type { DatabaseFiles } from '../actions.ts';
2
+ import { type DatabaseClient, type DatabaseSession } from '../client/types.ts';
3
+ import type { FileFormat, TableStructure } from '../protocol/index.ts';
4
+ import type { ImportSample } from './ImportDialog.tsx';
5
+ import { type ImportFormat } from './import-mapping.ts';
6
+ export type TransferNotice = {
7
+ readonly kind: 'exporting';
8
+ readonly format: FileFormat;
9
+ } | {
10
+ readonly kind: 'exported';
11
+ readonly rows: number;
12
+ } | {
13
+ readonly kind: 'imported';
14
+ readonly rows: number;
15
+ } | {
16
+ readonly kind: 'failed';
17
+ readonly message: string;
18
+ };
19
+ export interface ImportDraft {
20
+ readonly path: string;
21
+ readonly format: ImportFormat;
22
+ readonly header: boolean;
23
+ readonly sample: ImportSample | null;
24
+ readonly mapping: readonly (string | null)[];
25
+ readonly busy: boolean;
26
+ readonly error: string | null;
27
+ }
28
+ export interface TableTransferInput {
29
+ readonly client: DatabaseClient;
30
+ readonly session: DatabaseSession;
31
+ readonly files: DatabaseFiles | undefined;
32
+ readonly schema: string;
33
+ readonly table: string;
34
+ readonly structure: TableStructure | null;
35
+ readonly where: string;
36
+ readonly orderBy: string;
37
+ readonly onImported: () => void;
38
+ }
39
+ export interface TableTransfer {
40
+ readonly notice: TransferNotice | null;
41
+ readonly importDraft: ImportDraft | null;
42
+ exportAs(format: FileFormat): void;
43
+ cancelExport(): void;
44
+ dismiss(): void;
45
+ chooseImportFile(): void;
46
+ changeHeader(header: boolean): void;
47
+ changeMapping(fileColumn: number, tableColumn: string | null): void;
48
+ runImport(): void;
49
+ closeImport(): void;
50
+ }
51
+ export declare function useTableTransfer({ client, session, files, schema, table, structure, where, orderBy, onImported }: TableTransferInput): TableTransfer;
@@ -0,0 +1,125 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+ import { messageOf } from '@adecore/ui';
3
+ import { DatabaseRequestError } from '../client/types.js';
4
+ import { formatOfPath, importableColumns, mapColumn, matchColumns } from './import-mapping.js';
5
+ /* Export of the table as filtered, and import of a file into it, with the state both need between the dialogs and the strip. */
6
+ export function useTableTransfer({ client, session, files, schema, table, structure, where, orderBy, onImported }) {
7
+ const [notice, setNotice] = useState(null);
8
+ const [importDraft, setImportDraft] = useState(null);
9
+ const exporting = useRef(null);
10
+ const sampling = useRef(null);
11
+ const tableColumns = structure === null ? [] : importableColumns(structure.columns).map((column) => column.name);
12
+ useEffect(() => {
13
+ if (notice?.kind !== 'exported' && notice?.kind !== 'imported') {
14
+ return;
15
+ }
16
+ const timer = setTimeout(() => setNotice(null), 8000);
17
+ return () => clearTimeout(timer);
18
+ }, [notice]);
19
+ useEffect(() => () => sampling.current?.abort(), []);
20
+ const exportAs = (format) => {
21
+ if (files === undefined || exporting.current !== null) {
22
+ return;
23
+ }
24
+ void (async () => {
25
+ const path = await files.save({ suggestedName: `${table}.${format}`, format });
26
+ if (path === null) {
27
+ return;
28
+ }
29
+ const controller = new AbortController();
30
+ exporting.current = controller;
31
+ setNotice({ kind: 'exporting', format });
32
+ try {
33
+ const result = await session.export({ source: { kind: 'table', schema, table, where: where || undefined, orderBy: orderBy || undefined }, format, path }, { signal: controller.signal });
34
+ setNotice({ kind: 'exported', rows: result.rows });
35
+ }
36
+ catch (error) {
37
+ const cancelled = controller.signal.aborted || (error instanceof DatabaseRequestError && error.code === 'cancelled');
38
+ setNotice(cancelled ? null : { kind: 'failed', message: messageOf(error) });
39
+ }
40
+ finally {
41
+ exporting.current = null;
42
+ }
43
+ })();
44
+ };
45
+ const sample = async (path, format, header) => {
46
+ sampling.current?.abort();
47
+ const controller = new AbortController();
48
+ sampling.current = controller;
49
+ return await client.sample(path, format, header, { signal: controller.signal });
50
+ };
51
+ const chooseImportFile = () => {
52
+ if (files === undefined || structure === null) {
53
+ return;
54
+ }
55
+ void (async () => {
56
+ const path = await files.open({ formats: ['csv', 'tsv'] });
57
+ if (path === null) {
58
+ return;
59
+ }
60
+ const format = formatOfPath(path);
61
+ try {
62
+ const read = await sample(path, format, true);
63
+ setImportDraft({ path, format, header: true, sample: read, mapping: matchColumns(read.columns, tableColumns, true), busy: false, error: null });
64
+ }
65
+ catch (error) {
66
+ setNotice({ kind: 'failed', message: messageOf(error) });
67
+ }
68
+ })();
69
+ };
70
+ const changeHeader = (header) => {
71
+ if (importDraft === null) {
72
+ return;
73
+ }
74
+ const { path, format } = importDraft;
75
+ setImportDraft({ ...importDraft, header, sample: null, error: null });
76
+ void (async () => {
77
+ try {
78
+ const read = await sample(path, format, header);
79
+ setImportDraft((now) => now === null || now.path !== path ? now : { ...now, header, sample: read, mapping: matchColumns(read.columns, tableColumns, header) });
80
+ }
81
+ catch (error) {
82
+ if (!(error instanceof DatabaseRequestError && error.code === 'cancelled')) {
83
+ setImportDraft((now) => (now === null || now.path !== path ? now : { ...now, error: messageOf(error) }));
84
+ }
85
+ }
86
+ })();
87
+ };
88
+ const changeMapping = (fileColumn, tableColumn) => {
89
+ setImportDraft((now) => (now === null ? now : { ...now, mapping: mapColumn(now.mapping, fileColumn, tableColumn) }));
90
+ };
91
+ const runImport = () => {
92
+ if (importDraft === null || importDraft.busy || importDraft.sample === null) {
93
+ return;
94
+ }
95
+ const { path, format, header, mapping } = importDraft;
96
+ setImportDraft({ ...importDraft, busy: true, error: null });
97
+ void (async () => {
98
+ try {
99
+ const rows = await session.import(schema, table, { path, format, header, columns: mapping });
100
+ setImportDraft(null);
101
+ setNotice({ kind: 'imported', rows });
102
+ onImported();
103
+ }
104
+ catch (error) {
105
+ setImportDraft((now) => (now === null ? now : { ...now, busy: false, error: messageOf(error) }));
106
+ }
107
+ })();
108
+ };
109
+ const closeImport = () => {
110
+ sampling.current?.abort();
111
+ setImportDraft(null);
112
+ };
113
+ return {
114
+ notice,
115
+ importDraft,
116
+ exportAs,
117
+ cancelExport: () => exporting.current?.abort(),
118
+ dismiss: () => setNotice(null),
119
+ chooseImportFile,
120
+ changeHeader,
121
+ changeMapping,
122
+ runImport,
123
+ closeImport
124
+ };
125
+ }
@@ -0,0 +1,21 @@
1
+ import type { ColumnInfo, DockerContainer, ForeignKeyInfo, IndexInfo, ServerInfo, TableKind, Value } from '../protocol/index.ts';
2
+ import type { DatabaseTransport } from '../client/types.ts';
3
+ export interface FakeTable {
4
+ readonly kind?: TableKind;
5
+ readonly columns: readonly ColumnInfo[];
6
+ readonly primaryKey?: readonly string[];
7
+ readonly rows: readonly (readonly Value[])[];
8
+ readonly indexes?: readonly IndexInfo[];
9
+ readonly foreignKeys?: readonly ForeignKeyInfo[];
10
+ readonly ddl?: string;
11
+ }
12
+ export interface FakeDatabase {
13
+ readonly schemas: Readonly<Record<string, Readonly<Record<string, FakeTable>>>>;
14
+ }
15
+ export interface FakeDatabaseTransportOptions {
16
+ readonly databases: Readonly<Record<string, FakeDatabase>>;
17
+ readonly latencyMs?: number;
18
+ readonly server?: ServerInfo;
19
+ readonly containers?: readonly DockerContainer[];
20
+ }
21
+ export declare const fakeDatabaseTransport: (options: FakeDatabaseTransportOptions) => DatabaseTransport;
@@ -0,0 +1,372 @@
1
+ class FakeError extends Error {
2
+ code;
3
+ sqlState;
4
+ change;
5
+ constructor(code, message, sqlState, change) {
6
+ super(message);
7
+ this.code = code;
8
+ this.sqlState = sqlState;
9
+ this.change = change;
10
+ }
11
+ }
12
+ const SYSTEM_SCHEMAS = ['information_schema', 'mysql', 'performance_schema', 'sys'];
13
+ const DEFAULT_ROWS_CELL_LIMIT = 1024;
14
+ const DEFAULT_EXECUTE_LIMIT = 500;
15
+ const DEFAULT_EXECUTE_CELL_LIMIT = 65536;
16
+ const toTables = (database) => new Map(Object.entries(database.schemas).map(([schema, tables]) => [
17
+ schema,
18
+ new Map(Object.entries(tables).map(([name, table]) => [
19
+ name,
20
+ {
21
+ kind: table.kind ?? 'table',
22
+ columns: structuredClone([...table.columns]),
23
+ primaryKey: [...(table.primaryKey ?? [])],
24
+ rows: structuredClone(table.rows.map((row) => [...row])),
25
+ indexes: structuredClone([...(table.indexes ?? [])]),
26
+ foreignKeys: structuredClone([...(table.foreignKeys ?? [])]),
27
+ ddl: table.ddl ?? null
28
+ }
29
+ ]))
30
+ ]));
31
+ const findTable = (session, schema, name) => {
32
+ const tables = session.schemas.get(schema);
33
+ if (tables === undefined) {
34
+ throw new FakeError('query-failed', `Unknown database '${schema}'`, '42000');
35
+ }
36
+ const table = tables.get(name);
37
+ if (table === undefined) {
38
+ throw new FakeError('query-failed', `Table '${schema}.${name}' doesn't exist`, '42S02');
39
+ }
40
+ return table;
41
+ };
42
+ /* The columns that pick out one row: the primary key, or else the first unique index over columns that cannot be null. */
43
+ const rowKeyOf = (table) => {
44
+ if (table.primaryKey.length > 0) {
45
+ return table.primaryKey;
46
+ }
47
+ const unique = table.indexes.find((index) => index.unique && index.columns.every((name) => table.columns.find((column) => column.name === name)?.nullable === false));
48
+ return unique === undefined ? null : [...unique.columns];
49
+ };
50
+ const columnIndex = (table, name) => {
51
+ const index = table.columns.findIndex((column) => column.name === name);
52
+ if (index === -1) {
53
+ throw new FakeError('query-failed', `Unknown column '${name}' in 'field list'`, '42S22');
54
+ }
55
+ return index;
56
+ };
57
+ const isSameValue = (left, right) => {
58
+ if (left !== null && right !== null && typeof left === 'object' && typeof right === 'object') {
59
+ return left.hex === right.hex;
60
+ }
61
+ return left === right;
62
+ };
63
+ const matches = (table, row, key) => Object.entries(key).every(([name, value]) => isSameValue(row[columnIndex(table, name)], value));
64
+ const toCell = (value, cellLimit) => {
65
+ if (typeof value === 'string' && value.length > cellLimit) {
66
+ return { kind: 'longText', preview: value.slice(0, cellLimit), length: value.length };
67
+ }
68
+ if (value !== null && typeof value === 'object') {
69
+ const length = value.hex.length / 2;
70
+ return { kind: 'binary', hex: value.hex.slice(0, Math.min(length, cellLimit) * 2), length };
71
+ }
72
+ return value;
73
+ };
74
+ const toResultColumns = (table) => table.columns.map(({ name, type, kind }) => ({ name, type, kind }));
75
+ const toRowsResult = (table, rows, offset, limit, cellLimit) => ({
76
+ columns: toResultColumns(table),
77
+ rows: rows.slice(offset, offset + limit).map((row) => row.map((value) => toCell(value, cellLimit))),
78
+ hasMore: rows.length > offset + limit,
79
+ elapsedMs: 0
80
+ });
81
+ /* Evaluates the default of a column for a row an insert or an update leaves it out of. */
82
+ const defaultOf = (column, table) => {
83
+ if (column.autoIncrement) {
84
+ const index = table.columns.indexOf(column);
85
+ return table.rows.reduce((highest, row) => (typeof row[index] === 'number' ? Math.max(highest, row[index]) : highest), 0) + 1;
86
+ }
87
+ const expression = column.defaultValue?.trim() ?? null;
88
+ if (expression === null) {
89
+ if (column.nullable || column.generated) {
90
+ return null;
91
+ }
92
+ throw new FakeError('query-failed', `Field '${column.name}' doesn't have a default value`, 'HY000');
93
+ }
94
+ if (/^null$/i.test(expression)) {
95
+ return null;
96
+ }
97
+ if (/^current_timestamp(?:\(\d*\))?$/i.test(expression)) {
98
+ return new Date().toISOString().slice(0, 19).replace('T', ' ');
99
+ }
100
+ if (/^-?\d+(?:\.\d+)?$/.test(expression)) {
101
+ return Number(expression);
102
+ }
103
+ if (/^'.*'$/s.test(expression)) {
104
+ return expression.slice(1, -1).replace(/''/g, "'");
105
+ }
106
+ return expression;
107
+ };
108
+ const resolveEdit = (column, table, value) => {
109
+ const resolved = value !== null && typeof value === 'object' && value.kind === 'default' ? defaultOf(column, table) : value;
110
+ if (resolved === null && !column.nullable && !column.autoIncrement) {
111
+ throw new FakeError('query-failed', `Column '${column.name}' cannot be null`, '23000');
112
+ }
113
+ return resolved;
114
+ };
115
+ /* Applies the changes to a copy of the rows, so a failed change leaves the table as it was. */
116
+ const applyChanges = (table, changes) => {
117
+ if (table.kind === 'view') {
118
+ throw new FakeError('unsupported', 'A view cannot be edited.');
119
+ }
120
+ const key = rowKeyOf(table);
121
+ const draft = { ...table, rows: table.rows.map((row) => [...row]) };
122
+ changes.forEach((change, position) => {
123
+ if (change.kind === 'insert') {
124
+ for (const name of Object.keys(change.values)) {
125
+ columnIndex(table, name);
126
+ }
127
+ const row = table.columns.map((column) => {
128
+ const given = Object.hasOwn(change.values, column.name) ? change.values[column.name] : { kind: 'default' };
129
+ return resolveEdit(column, draft, given);
130
+ });
131
+ const primary = table.primaryKey.map((name) => row[columnIndex(table, name)]);
132
+ if (primary.length > 0 &&
133
+ draft.rows.some((existing) => table.primaryKey.every((name, i) => isSameValue(existing[columnIndex(table, name)], primary[i])))) {
134
+ throw new FakeError('query-failed', `Duplicate entry for key 'PRIMARY'`, '23000');
135
+ }
136
+ draft.rows.push(row);
137
+ return;
138
+ }
139
+ if (key === null) {
140
+ throw new FakeError('no-row-key', 'The table has no primary key or unique key to pick a row with.');
141
+ }
142
+ const found = draft.rows.filter((row) => matches(table, row, change.key));
143
+ if (found.length !== 1) {
144
+ throw new FakeError('conflict', `The ${change.kind} matched ${found.length === 0 ? 'no row' : 'more than one row'}.`, undefined, position);
145
+ }
146
+ const row = found[0];
147
+ if (change.kind === 'delete') {
148
+ draft.rows.splice(draft.rows.indexOf(row), 1);
149
+ return;
150
+ }
151
+ for (const [name, value] of Object.entries(change.values)) {
152
+ const index = columnIndex(table, name);
153
+ row[index] = resolveEdit(table.columns[index], draft, value);
154
+ }
155
+ });
156
+ table.rows = draft.rows;
157
+ return changes.length;
158
+ };
159
+ const SELECT_ALL = /^select\s+\*\s+from\s+(\S+)$/i;
160
+ /* The table of a `SELECT * FROM <table>`, the only statement the fake runs. */
161
+ const selectedTable = (session, sql) => {
162
+ const name = SELECT_ALL.exec(sql.trim().replace(/;+$/, ''))?.[1]?.replace(/^[`"]|[`"]$/g, '');
163
+ if (name === undefined) {
164
+ throw new FakeError('unsupported', 'The fake database only runs SELECT * FROM <table>.');
165
+ }
166
+ return findTable(session, session.schema ?? session.schemas.keys().next().value ?? '', name);
167
+ };
168
+ const runStatement = (session, sql, limit, cellLimit) => {
169
+ try {
170
+ const table = selectedTable(session, sql);
171
+ return { kind: 'rows', sql, ...toRowsResult(table, table.rows, 0, limit, cellLimit) };
172
+ }
173
+ catch (error) {
174
+ if (error instanceof FakeError) {
175
+ return {
176
+ kind: 'error',
177
+ sql,
178
+ error: { code: error.code, message: error.message, ...(error.sqlState === undefined ? {} : { sqlState: error.sqlState }) },
179
+ elapsedMs: 0
180
+ };
181
+ }
182
+ throw error;
183
+ }
184
+ };
185
+ /* Makes a schema the selected one, as a call that names `schema` does. */
186
+ const selectSchema = (session, schema) => {
187
+ if (schema === undefined) {
188
+ return;
189
+ }
190
+ if (!session.schemas.has(schema)) {
191
+ throw new FakeError('query-failed', `Unknown database '${schema}'`, '42000');
192
+ }
193
+ session.schema = schema;
194
+ };
195
+ /*
196
+ * An in-memory database server behind the transport of the client, for demos and for tests of an app.
197
+ * It answers the whole protocol, with these limits: `where` and `orderBy` are not interpreted, so
198
+ * `rows` and `count` return every row in stored order; `execute` and `page` run `SELECT * FROM <table>`
199
+ * on the selected schema (the first one until a call names another) and answer every other statement
200
+ * with an `unsupported` error; and, as in the protocol, the first failed statement ends the list.
201
+ * `transaction` only raises a flag that `execute` reports: a rollback does not undo a change. There
202
+ * are no files, so `export`, `import` and `sample` answer `unsupported`.
203
+ */
204
+ export const fakeDatabaseTransport = (options) => {
205
+ const databases = new Map(Object.entries(options.databases).map(([name, database]) => [name, toTables(database)]));
206
+ const sessions = new Map();
207
+ const waiting = new Map();
208
+ let counter = 0;
209
+ const serverOf = (config) => options.server ?? (config.engine === 'sqlite' ? { flavor: 'sqlite', version: '3.50.4' } : { flavor: 'mysql', version: '8.4.0' });
210
+ const connect = (config) => {
211
+ const name = config.engine === 'sqlite' ? config.path : config.host;
212
+ const schemas = databases.get(name);
213
+ if (schemas === undefined) {
214
+ throw new FakeError('connect-failed', config.engine === 'sqlite' ? `Unable to open database file "${name}"` : `Unknown MySQL server "${name}"`);
215
+ }
216
+ return { schemas, readOnly: config.readOnly === true, schema: config.engine === 'mysql' ? config.database || null : null, inTransaction: false };
217
+ };
218
+ const sessionOf = (id) => {
219
+ const session = sessions.get(id);
220
+ if (session === undefined) {
221
+ throw new FakeError('unknown-session', `There is no open session "${id}".`);
222
+ }
223
+ return session;
224
+ };
225
+ const handlers = {
226
+ open: ({ connection }) => {
227
+ const session = connect(connection);
228
+ const id = `fake-${++counter}`;
229
+ sessions.set(id, session);
230
+ return { session: id, server: serverOf(connection) };
231
+ },
232
+ close: ({ session }) => {
233
+ sessionOf(session);
234
+ sessions.delete(session);
235
+ return null;
236
+ },
237
+ test: ({ connection }) => {
238
+ connect(connection);
239
+ return { server: serverOf(connection) };
240
+ },
241
+ schemas: ({ session }) => ({
242
+ schemas: [...sessionOf(session).schemas.keys()].map((name) => ({ name, system: SYSTEM_SCHEMAS.includes(name) }))
243
+ }),
244
+ tables: ({ session, schema }) => {
245
+ const tables = sessionOf(session).schemas.get(schema);
246
+ if (tables === undefined) {
247
+ throw new FakeError('query-failed', `Unknown database '${schema}'`, '42000');
248
+ }
249
+ return { tables: [...tables].map(([name, table]) => ({ name, kind: table.kind, rowEstimate: table.rows.length, comment: null })) };
250
+ },
251
+ structure: ({ session, schema, table: name }) => {
252
+ const table = findTable(sessionOf(session), schema, name);
253
+ return {
254
+ schema,
255
+ name,
256
+ kind: table.kind,
257
+ columns: table.columns,
258
+ primaryKey: table.primaryKey,
259
+ rowKey: rowKeyOf(table),
260
+ indexes: table.indexes,
261
+ foreignKeys: table.foreignKeys,
262
+ ddl: table.ddl
263
+ };
264
+ },
265
+ rows: ({ session, schema, table: name, offset, limit, cellLimit }) => {
266
+ const table = findTable(sessionOf(session), schema, name);
267
+ return toRowsResult(table, table.rows, offset, limit, cellLimit ?? DEFAULT_ROWS_CELL_LIMIT);
268
+ },
269
+ count: ({ session, schema, table: name }) => ({ count: findTable(sessionOf(session), schema, name).rows.length }),
270
+ cell: ({ session, schema, table: name, key, column }) => {
271
+ const table = findTable(sessionOf(session), schema, name);
272
+ const index = columnIndex(table, column);
273
+ if (rowKeyOf(table) === null) {
274
+ throw new FakeError('no-row-key', 'The table has no primary key or unique key to pick a row with.');
275
+ }
276
+ const row = table.rows.find((candidate) => matches(table, candidate, key));
277
+ if (row === undefined) {
278
+ throw new FakeError('query-failed', 'No row matches the key.');
279
+ }
280
+ return { value: row[index] };
281
+ },
282
+ apply: ({ session, schema, table: name, changes }) => {
283
+ const target = sessionOf(session);
284
+ if (target.readOnly) {
285
+ throw new FakeError('read-only', 'The connection is read only.');
286
+ }
287
+ return { affected: applyChanges(findTable(target, schema, name), changes) };
288
+ },
289
+ execute: ({ session, sql, schema, limit, cellLimit }) => {
290
+ const target = sessionOf(session);
291
+ selectSchema(target, schema);
292
+ const results = [];
293
+ for (const statement of sql
294
+ .split(';')
295
+ .map((part) => part.trim())
296
+ .filter((part) => part.length > 0)) {
297
+ const result = runStatement(target, statement, limit ?? DEFAULT_EXECUTE_LIMIT, cellLimit ?? DEFAULT_EXECUTE_CELL_LIMIT);
298
+ results.push(result);
299
+ if (result.kind === 'error') {
300
+ break;
301
+ }
302
+ }
303
+ return { results, inTransaction: target.inTransaction };
304
+ },
305
+ page: ({ session, sql, schema, offset, limit, cellLimit }) => {
306
+ const target = sessionOf(session);
307
+ selectSchema(target, schema);
308
+ const table = selectedTable(target, sql);
309
+ return toRowsResult(table, table.rows, offset, limit, cellLimit ?? DEFAULT_EXECUTE_CELL_LIMIT);
310
+ },
311
+ transaction: ({ session, action }) => {
312
+ const target = sessionOf(session);
313
+ target.inTransaction = action === 'begin';
314
+ return { active: target.inTransaction };
315
+ },
316
+ export: ({ session }) => {
317
+ sessionOf(session);
318
+ throw new FakeError('unsupported', 'The fake database has no files to export to.');
319
+ },
320
+ sample: () => {
321
+ throw new FakeError('unsupported', 'The fake database has no files to read.');
322
+ },
323
+ import: ({ session }) => {
324
+ sessionOf(session);
325
+ throw new FakeError('unsupported', 'The fake database has no files to import from.');
326
+ },
327
+ discover: () => ({ containers: structuredClone([...(options.containers ?? [])]) }),
328
+ cancel: ({ request }) => {
329
+ const stop = waiting.get(request);
330
+ stop?.();
331
+ return { cancelled: stop !== undefined };
332
+ }
333
+ };
334
+ const answer = (request) => {
335
+ try {
336
+ const handler = handlers[request.method];
337
+ return { id: request.id, ok: true, result: handler(request.params) };
338
+ }
339
+ catch (error) {
340
+ if (error instanceof FakeError) {
341
+ return {
342
+ id: request.id,
343
+ ok: false,
344
+ error: {
345
+ code: error.code,
346
+ message: error.message,
347
+ ...(error.sqlState === undefined ? {} : { sqlState: error.sqlState }),
348
+ ...(error.change === undefined ? {} : { change: error.change })
349
+ }
350
+ };
351
+ }
352
+ throw error;
353
+ }
354
+ };
355
+ return (request) => {
356
+ // `cancel` answers at once, since delaying it would let the request it targets finish first.
357
+ if (!options.latencyMs || request.method === 'cancel') {
358
+ return Promise.resolve(answer(request));
359
+ }
360
+ return new Promise((resolve) => {
361
+ const timer = setTimeout(() => {
362
+ waiting.delete(request.id);
363
+ resolve(answer(request));
364
+ }, options.latencyMs);
365
+ waiting.set(request.id, () => {
366
+ clearTimeout(timer);
367
+ waiting.delete(request.id);
368
+ resolve({ id: request.id, ok: false, error: { code: 'cancelled', message: 'The request was cancelled.' } });
369
+ });
370
+ });
371
+ };
372
+ };
@@ -0,0 +1 @@
1
+ export { fakeDatabaseTransport, type FakeDatabase, type FakeDatabaseTransportOptions, type FakeTable } from './fake.ts';
@@ -0,0 +1 @@
1
+ export { fakeDatabaseTransport } from './fake.js';
@@ -0,0 +1 @@
1
+ export declare function usePopupPress(): () => boolean;
@@ -0,0 +1,24 @@
1
+ import { useEffect, useRef } from 'react';
2
+ import { isInFloatingLayer } from '@adecore/ui';
3
+ /*
4
+ * Catches the click that follows a press in a popup, menu or dialog. When a popup closes on that
5
+ * press, the browser lands the click on whatever lies under the pointer, which is not something the
6
+ * person clicked. The returned check is true once for such a click.
7
+ */
8
+ export function usePopupPress() {
9
+ const pressed = useRef(false);
10
+ useEffect(() => {
11
+ const press = (e) => {
12
+ pressed.current = isInFloatingLayer(e.target);
13
+ };
14
+ document.addEventListener('pointerdown', press, true);
15
+ return () => {
16
+ document.removeEventListener('pointerdown', press, true);
17
+ };
18
+ }, []);
19
+ return () => {
20
+ const was = pressed.current;
21
+ pressed.current = false;
22
+ return was;
23
+ };
24
+ }
@@ -0,0 +1 @@
1
+ export declare function useSchemaChange(connectionId: string, schema: string, onChange: () => void): void;
@@ -0,0 +1,13 @@
1
+ import { useEffect } from 'react';
2
+ import { useDatabaseClient } from './client-context.js';
3
+ import { useStableCallback } from './use-stable-callback.js';
4
+ /* Calls `onChange` when the shape of this schema changed, or of its connection when the change names no schema. */
5
+ export function useSchemaChange(connectionId, schema, onChange) {
6
+ const client = useDatabaseClient();
7
+ const notify = useStableCallback(onChange);
8
+ useEffect(() => client.onSchemaChange((change) => {
9
+ if (change.connectionId === connectionId && (change.schema === undefined || change.schema === schema)) {
10
+ notify();
11
+ }
12
+ }), [client, connectionId, schema, notify]);
13
+ }
@@ -0,0 +1 @@
1
+ export declare function useStableCallback<Args extends unknown[], Result>(callback: (...args: Args) => Result): (...args: Args) => Result;
@@ -0,0 +1,9 @@
1
+ import { useCallback, useLayoutEffect, useRef } from 'react';
2
+ /* A function that never changes identity and always calls the latest `callback`, so a provider value built on it does not rebuild on each render. */
3
+ export function useStableCallback(callback) {
4
+ const latest = useRef(callback);
5
+ useLayoutEffect(() => {
6
+ latest.current = callback;
7
+ });
8
+ return useCallback((...args) => latest.current(...args), []);
9
+ }