@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.
- package/LICENSE +21 -0
- package/README.md +152 -1
- package/dist/DatabaseProvider.d.ts +13 -0
- package/dist/DatabaseProvider.js +17 -0
- package/dist/actions.d.ts +40 -0
- package/dist/actions.js +1 -0
- package/dist/client/client.d.ts +5 -0
- package/dist/client/client.js +245 -0
- package/dist/client/index.d.ts +2 -0
- package/dist/client/index.js +2 -0
- package/dist/client/types.d.ts +97 -0
- package/dist/client/types.js +13 -0
- package/dist/client-context.d.ts +16 -0
- package/dist/client-context.js +17 -0
- package/dist/connections/ConnectionForm.d.ts +10 -0
- package/dist/connections/ConnectionForm.js +145 -0
- package/dist/connections/ConnectionManager.d.ts +12 -0
- package/dist/connections/ConnectionManager.js +93 -0
- package/dist/connections/EngineIcon.d.ts +9 -0
- package/dist/connections/EngineIcon.js +7 -0
- package/dist/connections/connection-config.d.ts +16 -0
- package/dist/connections/connection-config.js +107 -0
- package/dist/connections/docker.d.ts +9 -0
- package/dist/connections/docker.js +38 -0
- package/dist/connections/engine-icons.d.ts +3 -0
- package/dist/connections/engine-icons.js +3 -0
- package/dist/connections/use-docker-containers.d.ts +17 -0
- package/dist/connections/use-docker-containers.js +26 -0
- package/dist/console/DestructiveDialog.d.ts +9 -0
- package/dist/console/DestructiveDialog.js +9 -0
- package/dist/console/HistoryPanel.d.ts +10 -0
- package/dist/console/HistoryPanel.js +15 -0
- package/dist/console/QueryConsole.d.ts +14 -0
- package/dist/console/QueryConsole.js +261 -0
- package/dist/console/SchemaPicker.d.ts +11 -0
- package/dist/console/SchemaPicker.js +8 -0
- package/dist/console/SqlEditor.d.ts +12 -0
- package/dist/console/SqlEditor.js +61 -0
- package/dist/console/StatementResultView.d.ts +18 -0
- package/dist/console/StatementResultView.js +32 -0
- package/dist/console/TransactionControls.d.ts +13 -0
- package/dist/console/TransactionControls.js +12 -0
- package/dist/console/destructive.d.ts +7 -0
- package/dist/console/destructive.js +43 -0
- package/dist/console/editing.d.ts +12 -0
- package/dist/console/editing.js +60 -0
- package/dist/console/history.d.ts +13 -0
- package/dist/console/history.js +43 -0
- package/dist/console/labels.d.ts +1 -0
- package/dist/console/labels.js +6 -0
- package/dist/console/outcome.d.ts +6 -0
- package/dist/console/outcome.js +11 -0
- package/dist/console/schemas.d.ts +2 -0
- package/dist/console/schemas.js +5 -0
- package/dist/console/shortcuts.d.ts +2 -0
- package/dist/console/shortcuts.js +3 -0
- package/dist/console/useConsoleHistory.d.ts +7 -0
- package/dist/console/useConsoleHistory.js +22 -0
- package/dist/ddl/alter-mysql.d.ts +4 -0
- package/dist/ddl/alter-mysql.js +79 -0
- package/dist/ddl/alter-sqlite.d.ts +4 -0
- package/dist/ddl/alter-sqlite.js +80 -0
- package/dist/ddl/alter.d.ts +4 -0
- package/dist/ddl/alter.js +24 -0
- package/dist/ddl/column.d.ts +9 -0
- package/dist/ddl/column.js +62 -0
- package/dist/ddl/create.d.ts +12 -0
- package/dist/ddl/create.js +74 -0
- package/dist/ddl/dialect.d.ts +12 -0
- package/dist/ddl/dialect.js +34 -0
- package/dist/ddl/diff.d.ts +34 -0
- package/dist/ddl/diff.js +111 -0
- package/dist/ddl/draft.d.ts +46 -0
- package/dist/ddl/draft.js +70 -0
- package/dist/ddl/index.d.ts +10 -0
- package/dist/ddl/index.js +10 -0
- package/dist/ddl/parse.d.ts +17 -0
- package/dist/ddl/parse.js +131 -0
- package/dist/ddl/statements.d.ts +6 -0
- package/dist/ddl/statements.js +12 -0
- package/dist/ddl/types.d.ts +6 -0
- package/dist/ddl/types.js +52 -0
- package/dist/ddl/validate.d.ts +7 -0
- package/dist/ddl/validate.js +38 -0
- package/dist/designer/ColumnsEditor.d.ts +2 -0
- package/dist/designer/ColumnsEditor.js +24 -0
- package/dist/designer/DesignerView.d.ts +25 -0
- package/dist/designer/DesignerView.js +33 -0
- package/dist/designer/ForeignKeysEditor.d.ts +7 -0
- package/dist/designer/ForeignKeysEditor.js +24 -0
- package/dist/designer/IndexesEditor.d.ts +2 -0
- package/dist/designer/IndexesEditor.js +13 -0
- package/dist/designer/NamePicker.d.ts +11 -0
- package/dist/designer/NamePicker.js +13 -0
- package/dist/designer/OptionsEditor.d.ts +2 -0
- package/dist/designer/OptionsEditor.js +17 -0
- package/dist/designer/SqlPreview.d.ts +12 -0
- package/dist/designer/SqlPreview.js +17 -0
- package/dist/designer/TableDesigner.d.ts +10 -0
- package/dist/designer/TableDesigner.js +103 -0
- package/dist/designer/edit.d.ts +17 -0
- package/dist/designer/edit.js +115 -0
- package/dist/designer/editor-props.d.ts +10 -0
- package/dist/designer/editor-props.js +1 -0
- package/dist/designer/plan.d.ts +9 -0
- package/dist/designer/plan.js +13 -0
- package/dist/designer/recover.d.ts +2 -0
- package/dist/designer/recover.js +18 -0
- package/dist/designer/useReferenceColumns.d.ts +3 -0
- package/dist/designer/useReferenceColumns.js +36 -0
- package/dist/explorer/DatabaseExplorer.d.ts +13 -0
- package/dist/explorer/DatabaseExplorer.js +264 -0
- package/dist/explorer/RowMenu.d.ts +11 -0
- package/dist/explorer/RowMenu.js +89 -0
- package/dist/explorer/TableDialog.d.ts +15 -0
- package/dist/explorer/TableDialog.js +50 -0
- package/dist/explorer/expansion.d.ts +7 -0
- package/dist/explorer/expansion.js +31 -0
- package/dist/explorer/table-statements.d.ts +5 -0
- package/dist/explorer/table-statements.js +12 -0
- package/dist/explorer/tree.d.ts +125 -0
- package/dist/explorer/tree.js +295 -0
- package/dist/explorer/use-explorer-loads.d.ts +12 -0
- package/dist/explorer/use-explorer-loads.js +109 -0
- package/dist/explorer/use-stored-expansion.d.ts +9 -0
- package/dist/explorer/use-stored-expansion.js +47 -0
- package/dist/grid/CellEditor.d.ts +9 -0
- package/dist/grid/CellEditor.js +29 -0
- package/dist/grid/CopyAsMenu.d.ts +7 -0
- package/dist/grid/CopyAsMenu.js +10 -0
- package/dist/grid/DataGrid.d.ts +36 -0
- package/dist/grid/DataGrid.js +512 -0
- package/dist/grid/EnumPicker.d.ts +17 -0
- package/dist/grid/EnumPicker.js +53 -0
- package/dist/grid/GridHeaderCell.d.ts +36 -0
- package/dist/grid/GridHeaderCell.js +69 -0
- package/dist/grid/RecordView.d.ts +20 -0
- package/dist/grid/RecordView.js +126 -0
- package/dist/grid/ValueDock.d.ts +11 -0
- package/dist/grid/ValueDock.js +28 -0
- package/dist/grid/aggregates.d.ts +22 -0
- package/dist/grid/aggregates.js +62 -0
- package/dist/grid/cell-range.d.ts +14 -0
- package/dist/grid/cell-range.js +11 -0
- package/dist/grid/column-layout.d.ts +18 -0
- package/dist/grid/column-layout.js +49 -0
- package/dist/grid/column-selection.d.ts +11 -0
- package/dist/grid/column-selection.js +28 -0
- package/dist/grid/column-view.d.ts +10 -0
- package/dist/grid/column-view.js +23 -0
- package/dist/grid/copy-formats.d.ts +15 -0
- package/dist/grid/copy-formats.js +80 -0
- package/dist/grid/display.d.ts +20 -0
- package/dist/grid/display.js +81 -0
- package/dist/grid/edit-value.d.ts +5 -0
- package/dist/grid/edit-value.js +55 -0
- package/dist/grid/enum-type.d.ts +7 -0
- package/dist/grid/enum-type.js +52 -0
- package/dist/grid/focused-value.d.ts +4 -0
- package/dist/grid/focused-value.js +19 -0
- package/dist/grid/layout.d.ts +22 -0
- package/dist/grid/layout.js +74 -0
- package/dist/grid/navigation.d.ts +15 -0
- package/dist/grid/navigation.js +52 -0
- package/dist/grid/row-selection.d.ts +9 -0
- package/dist/grid/row-selection.js +24 -0
- package/dist/grid/sort.d.ts +7 -0
- package/dist/grid/sort.js +7 -0
- package/dist/grid/types.d.ts +30 -0
- package/dist/grid/types.js +9 -0
- package/dist/host/helper-path.d.ts +7 -0
- package/dist/host/helper-path.js +15 -0
- package/dist/host/host.d.ts +2 -0
- package/dist/host/host.js +343 -0
- package/dist/host/index.d.ts +5 -0
- package/dist/host/index.js +4 -0
- package/dist/host/spawn.d.ts +7 -0
- package/dist/host/spawn.js +98 -0
- package/dist/host/types.d.ts +19 -0
- package/dist/host/types.js +1 -0
- package/dist/host/validate.d.ts +10 -0
- package/dist/host/validate.js +339 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +13 -0
- package/dist/locales/en.json +577 -0
- package/dist/locales/nl.json +577 -0
- package/dist/locales.d.ts +4 -0
- package/dist/locales.js +14 -0
- package/dist/protocol/connection.d.ts +56 -0
- package/dist/protocol/connection.js +1 -0
- package/dist/protocol/errors.d.ts +7 -0
- package/dist/protocol/errors.js +1 -0
- package/dist/protocol/index.d.ts +5 -0
- package/dist/protocol/index.js +3 -0
- package/dist/protocol/messages.d.ts +250 -0
- package/dist/protocol/messages.js +2 -0
- package/dist/protocol/schema.d.ts +53 -0
- package/dist/protocol/schema.js +1 -0
- package/dist/protocol/values.d.ts +21 -0
- package/dist/protocol/values.js +15 -0
- package/dist/sql-split.d.ts +15 -0
- package/dist/sql-split.js +201 -0
- package/dist/sql.d.ts +29 -0
- package/dist/sql.js +104 -0
- package/dist/structure/StructureView.d.ts +14 -0
- package/dist/structure/StructureView.js +99 -0
- package/dist/structure/structure-text.d.ts +2 -0
- package/dist/structure/structure-text.js +2 -0
- package/dist/table/CommandField.d.ts +14 -0
- package/dist/table/CommandField.js +157 -0
- package/dist/table/ImportDialog.d.ts +27 -0
- package/dist/table/ImportDialog.js +28 -0
- package/dist/table/TableStatusBar.d.ts +19 -0
- package/dist/table/TableStatusBar.js +22 -0
- package/dist/table/TableToolbar.d.ts +43 -0
- package/dist/table/TableToolbar.js +16 -0
- package/dist/table/TableView.d.ts +13 -0
- package/dist/table/TableView.js +508 -0
- package/dist/table/command-field.d.ts +57 -0
- package/dist/table/command-field.js +203 -0
- package/dist/table/grid-rows.d.ts +23 -0
- package/dist/table/grid-rows.js +55 -0
- package/dist/table/import-mapping.d.ts +7 -0
- package/dist/table/import-mapping.js +28 -0
- package/dist/table/layout-store.d.ts +13 -0
- package/dist/table/layout-store.js +43 -0
- package/dist/table/paging.d.ts +12 -0
- package/dist/table/paging.js +20 -0
- package/dist/table/pending.d.ts +32 -0
- package/dist/table/pending.js +94 -0
- package/dist/table/references.d.ts +12 -0
- package/dist/table/references.js +21 -0
- package/dist/table/useLoaded.d.ts +7 -0
- package/dist/table/useLoaded.js +36 -0
- package/dist/table/useStoredLayout.d.ts +8 -0
- package/dist/table/useStoredLayout.js +39 -0
- package/dist/table/useTableTransfer.d.ts +51 -0
- package/dist/table/useTableTransfer.js +125 -0
- package/dist/testing/fake.d.ts +21 -0
- package/dist/testing/fake.js +372 -0
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.js +1 -0
- package/dist/use-popup-press.d.ts +1 -0
- package/dist/use-popup-press.js +24 -0
- package/dist/use-schema-change.d.ts +1 -0
- package/dist/use-schema-change.js +13 -0
- package/dist/use-stable-callback.d.ts +1 -0
- package/dist/use-stable-callback.js +9 -0
- package/dist/value/ValuePanel.d.ts +14 -0
- package/dist/value/ValuePanel.js +103 -0
- package/dist/value/draft.d.ts +33 -0
- package/dist/value/draft.js +44 -0
- package/dist/value/hex.d.ts +14 -0
- package/dist/value/hex.js +46 -0
- package/dist/value/json.d.ts +9 -0
- package/dist/value/json.js +71 -0
- package/dist/value/views.d.ts +22 -0
- package/dist/value/views.js +54 -0
- package/dist/workbench/DatabaseWorkbench.d.ts +9 -0
- package/dist/workbench/DatabaseWorkbench.js +179 -0
- package/dist/workbench/DesignerTab.d.ts +10 -0
- package/dist/workbench/DesignerTab.js +22 -0
- package/dist/workbench/TableTab.d.ts +10 -0
- package/dist/workbench/TableTab.js +8 -0
- package/dist/workbench/tabs.d.ts +42 -0
- package/dist/workbench/tabs.js +139 -0
- package/package.json +79 -3
- package/src/DatabaseProvider.tsx +37 -0
- package/src/actions.ts +34 -0
- package/src/client/client.ts +324 -0
- package/src/client/index.ts +18 -0
- package/src/client/types.ts +161 -0
- package/src/client-context.ts +35 -0
- package/src/connections/ConnectionForm.tsx +474 -0
- package/src/connections/ConnectionManager.tsx +224 -0
- package/src/connections/EngineIcon.tsx +18 -0
- package/src/connections/connection-config.ts +143 -0
- package/src/connections/docker.ts +50 -0
- package/src/connections/engine-icons.ts +5 -0
- package/src/connections/use-docker-containers.ts +41 -0
- package/src/console/DestructiveDialog.tsx +40 -0
- package/src/console/HistoryPanel.tsx +67 -0
- package/src/console/QueryConsole.tsx +439 -0
- package/src/console/SchemaPicker.tsx +32 -0
- package/src/console/SqlEditor.tsx +95 -0
- package/src/console/StatementResultView.tsx +176 -0
- package/src/console/TransactionControls.tsx +53 -0
- package/src/console/destructive.ts +60 -0
- package/src/console/editing.ts +77 -0
- package/src/console/history.ts +62 -0
- package/src/console/labels.ts +7 -0
- package/src/console/outcome.ts +14 -0
- package/src/console/schemas.ts +7 -0
- package/src/console/shortcuts.ts +4 -0
- package/src/console/useConsoleHistory.ts +39 -0
- package/src/ddl/alter-mysql.ts +90 -0
- package/src/ddl/alter-sqlite.ts +93 -0
- package/src/ddl/alter.ts +29 -0
- package/src/ddl/column.ts +74 -0
- package/src/ddl/create.ts +92 -0
- package/src/ddl/dialect.ts +53 -0
- package/src/ddl/diff.ts +164 -0
- package/src/ddl/draft.ts +136 -0
- package/src/ddl/index.ts +10 -0
- package/src/ddl/parse.ts +161 -0
- package/src/ddl/statements.ts +20 -0
- package/src/ddl/types.ts +60 -0
- package/src/ddl/validate.ts +47 -0
- package/src/designer/ColumnsEditor.tsx +204 -0
- package/src/designer/DesignerView.tsx +184 -0
- package/src/designer/ForeignKeysEditor.tsx +132 -0
- package/src/designer/IndexesEditor.tsx +63 -0
- package/src/designer/NamePicker.tsx +44 -0
- package/src/designer/OptionsEditor.tsx +101 -0
- package/src/designer/SqlPreview.tsx +56 -0
- package/src/designer/TableDesigner.tsx +186 -0
- package/src/designer/edit.ts +137 -0
- package/src/designer/editor-props.ts +13 -0
- package/src/designer/plan.ts +25 -0
- package/src/designer/recover.ts +20 -0
- package/src/designer/useReferenceColumns.ts +54 -0
- package/src/explorer/DatabaseExplorer.tsx +450 -0
- package/src/explorer/RowMenu.tsx +134 -0
- package/src/explorer/TableDialog.tsx +80 -0
- package/src/explorer/expansion.ts +47 -0
- package/src/explorer/table-statements.ts +18 -0
- package/src/explorer/tree.ts +453 -0
- package/src/explorer/use-explorer-loads.ts +157 -0
- package/src/explorer/use-stored-expansion.ts +66 -0
- package/src/grid/CellEditor.tsx +53 -0
- package/src/grid/CopyAsMenu.tsx +33 -0
- package/src/grid/DataGrid.tsx +871 -0
- package/src/grid/EnumPicker.tsx +108 -0
- package/src/grid/GridHeaderCell.tsx +243 -0
- package/src/grid/RecordView.tsx +293 -0
- package/src/grid/ValueDock.tsx +52 -0
- package/src/grid/aggregates.ts +94 -0
- package/src/grid/cell-range.ts +26 -0
- package/src/grid/column-layout.ts +71 -0
- package/src/grid/column-selection.ts +42 -0
- package/src/grid/column-view.ts +35 -0
- package/src/grid/copy-formats.ts +108 -0
- package/src/grid/display.ts +109 -0
- package/src/grid/edit-value.ts +60 -0
- package/src/grid/enum-type.ts +62 -0
- package/src/grid/focused-value.ts +23 -0
- package/src/grid/layout.ts +91 -0
- package/src/grid/navigation.ts +74 -0
- package/src/grid/row-selection.ts +35 -0
- package/src/grid/sort.ts +12 -0
- package/src/grid/types.ts +59 -0
- package/src/host/helper-path.ts +41 -0
- package/src/host/host.ts +456 -0
- package/src/host/index.ts +5 -0
- package/src/host/spawn.ts +124 -0
- package/src/host/types.ts +46 -0
- package/src/host/validate.ts +402 -0
- package/src/index.ts +37 -0
- package/src/locales/en.json +577 -0
- package/src/locales/nl.json +577 -0
- package/src/locales.ts +18 -0
- package/src/protocol/connection.ts +86 -0
- package/src/protocol/errors.ts +38 -0
- package/src/protocol/index.ts +33 -0
- package/src/protocol/messages.ts +168 -0
- package/src/protocol/schema.ts +76 -0
- package/src/protocol/values.ts +52 -0
- package/src/sql-split.ts +220 -0
- package/src/sql.ts +152 -0
- package/src/structure/StructureView.tsx +269 -0
- package/src/structure/structure-text.ts +4 -0
- package/src/table/CommandField.tsx +318 -0
- package/src/table/ImportDialog.tsx +168 -0
- package/src/table/TableStatusBar.tsx +91 -0
- package/src/table/TableToolbar.tsx +217 -0
- package/src/table/TableView.tsx +880 -0
- package/src/table/command-field.ts +285 -0
- package/src/table/grid-rows.ts +80 -0
- package/src/table/import-mapping.ts +37 -0
- package/src/table/layout-store.ts +62 -0
- package/src/table/paging.ts +33 -0
- package/src/table/pending.ts +143 -0
- package/src/table/references.ts +38 -0
- package/src/table/useLoaded.ts +58 -0
- package/src/table/useStoredLayout.ts +53 -0
- package/src/table/useTableTransfer.ts +187 -0
- package/src/testing/fake.ts +533 -0
- package/src/testing/index.ts +1 -0
- package/src/use-popup-press.ts +27 -0
- package/src/use-schema-change.ts +19 -0
- package/src/use-stable-callback.ts +12 -0
- package/src/value/ValuePanel.tsx +254 -0
- package/src/value/draft.ts +72 -0
- package/src/value/hex.ts +62 -0
- package/src/value/json.ts +74 -0
- package/src/value/views.ts +68 -0
- package/src/workbench/DatabaseWorkbench.tsx +348 -0
- package/src/workbench/DesignerTab.tsx +37 -0
- package/src/workbench/TableTab.tsx +27 -0
- package/src/workbench/tabs.ts +184 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import type { ConnectionConfig, DatabaseMethod, DatabaseParams, DatabaseRequest, DatabaseResult, ServerInfo } from '../protocol/index.ts';
|
|
2
|
+
import {
|
|
3
|
+
DatabaseRequestError,
|
|
4
|
+
type Connection,
|
|
5
|
+
type DatabaseClient,
|
|
6
|
+
type DatabaseSession,
|
|
7
|
+
type DatabaseTransport,
|
|
8
|
+
type ExecuteOptions,
|
|
9
|
+
type RequestOptions,
|
|
10
|
+
type SchemaChange
|
|
11
|
+
} from './types.ts';
|
|
12
|
+
|
|
13
|
+
export interface DatabaseClientOptions {
|
|
14
|
+
/* Makes the id of a request. `crypto.randomUUID()` when left out. */
|
|
15
|
+
readonly createId?: () => string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface Opened {
|
|
19
|
+
readonly session: string;
|
|
20
|
+
readonly server: ServerInfo;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const cancelledError = (): DatabaseRequestError => new DatabaseRequestError('cancelled', 'The request was cancelled.');
|
|
24
|
+
|
|
25
|
+
const isUnknownSession = (error: unknown): boolean => error instanceof DatabaseRequestError && error.code === 'unknown-session';
|
|
26
|
+
|
|
27
|
+
const LEADING_COMMENTS = /^(?:\s+|--[^\n]*(?:\n|$)|#[^\n]*(?:\n|$)|\/\*[\s\S]*?\*\/)+/;
|
|
28
|
+
const CHANGES_SHAPE = /^(?:create|alter|drop|rename|truncate)\b/i;
|
|
29
|
+
|
|
30
|
+
const changesShape = (sql: string): boolean => CHANGES_SHAPE.test(sql.replace(LEADING_COMMENTS, ''));
|
|
31
|
+
|
|
32
|
+
/* Structural equality for the JSON a config is made of; a key set to `undefined` counts as absent. */
|
|
33
|
+
const isEqual = (left: unknown, right: unknown): boolean => {
|
|
34
|
+
if (left === right) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (typeof left !== 'object' || typeof right !== 'object' || left === null || right === null) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const leftKeys = Object.keys(left).filter((key) => (left as Record<string, unknown>)[key] !== undefined);
|
|
43
|
+
const rightKeys = Object.keys(right).filter((key) => (right as Record<string, unknown>)[key] !== undefined);
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
leftKeys.length === rightKeys.length &&
|
|
47
|
+
leftKeys.every((key) => isEqual((left as Record<string, unknown>)[key], (right as Record<string, unknown>)[key]))
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/* Rejects with `cancelled` as soon as the signal aborts, without waiting for the promise. */
|
|
52
|
+
const raceAbort = <T>(promise: Promise<T>, signal: AbortSignal | undefined): Promise<T> => {
|
|
53
|
+
if (signal === undefined) {
|
|
54
|
+
return promise;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (signal.aborted) {
|
|
58
|
+
promise.catch(() => {});
|
|
59
|
+
return Promise.reject(cancelledError());
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return new Promise<T>((resolve, reject) => {
|
|
63
|
+
const onAbort = (): void => reject(cancelledError());
|
|
64
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
65
|
+
promise.then(resolve, reject).finally(() => signal.removeEventListener('abort', onAbort));
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export const createDatabaseClient = (transport: DatabaseTransport, options: DatabaseClientOptions = {}): DatabaseClient => {
|
|
70
|
+
const createId = options.createId ?? (() => crypto.randomUUID());
|
|
71
|
+
const live = new Map<string, Map<string, ReturnType<typeof createSession>>>();
|
|
72
|
+
const schemaListeners = new Set<(change: SchemaChange) => void>();
|
|
73
|
+
|
|
74
|
+
const notifySchemaChange = (change: SchemaChange): void => {
|
|
75
|
+
for (const listener of [...schemaListeners]) {
|
|
76
|
+
try {
|
|
77
|
+
listener(change);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
// One failing listener must neither hide the result of the statement nor keep the others from hearing it.
|
|
80
|
+
console.error('A schema change listener failed.', error);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const send = async <M extends DatabaseMethod>(method: M, params: DatabaseParams<M>, signal?: AbortSignal): Promise<DatabaseResult<M>> => {
|
|
86
|
+
if (signal?.aborted) {
|
|
87
|
+
throw cancelledError();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const id = createId();
|
|
91
|
+
const request = { id, method, params } as DatabaseRequest;
|
|
92
|
+
|
|
93
|
+
return new Promise<DatabaseResult<M>>((resolve, reject) => {
|
|
94
|
+
const onAbort = (): void => {
|
|
95
|
+
const cancel = { id: createId(), method: 'cancel', params: { request: id } } as DatabaseRequest;
|
|
96
|
+
new Promise((done) => done(transport(cancel))).catch(() => {});
|
|
97
|
+
reject(cancelledError());
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
101
|
+
|
|
102
|
+
new Promise((done) => done(transport(request)))
|
|
103
|
+
.then(
|
|
104
|
+
(response) => {
|
|
105
|
+
const { ok, result, error } = response as {
|
|
106
|
+
ok: boolean;
|
|
107
|
+
result?: unknown;
|
|
108
|
+
error?: { code: never; message: string; sqlState?: string; change?: number };
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
if (ok) {
|
|
112
|
+
resolve(result as DatabaseResult<M>);
|
|
113
|
+
} else {
|
|
114
|
+
reject(new DatabaseRequestError(error!.code, error!.message, error!.sqlState, error!.change));
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
(error: unknown) => reject(new DatabaseRequestError('helper-unavailable', error instanceof Error ? error.message : String(error)))
|
|
118
|
+
)
|
|
119
|
+
.finally(() => signal?.removeEventListener('abort', onAbort));
|
|
120
|
+
});
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const createSession = (initial: Connection, channel: string) => {
|
|
124
|
+
let connection = initial;
|
|
125
|
+
let opened: Opened | null = null;
|
|
126
|
+
let opening: Promise<Opened> | null = null;
|
|
127
|
+
|
|
128
|
+
/* A session that was closed and is used again is findable again, so `disconnect` and `dispose` reach it. */
|
|
129
|
+
const track = (): void => {
|
|
130
|
+
let channels = live.get(connection.id);
|
|
131
|
+
|
|
132
|
+
if (channels === undefined) {
|
|
133
|
+
channels = new Map();
|
|
134
|
+
live.set(connection.id, channels);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!channels.has(channel)) {
|
|
138
|
+
channels.set(channel, created);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const open = (): Promise<Opened> => {
|
|
143
|
+
if (opened !== null) {
|
|
144
|
+
return Promise.resolve(opened);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
track();
|
|
148
|
+
opening ??= send('open', { connection: connection.config })
|
|
149
|
+
.then((result) => (opened = result))
|
|
150
|
+
.finally(() => {
|
|
151
|
+
opening = null;
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
return opening;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
/* Reads are safe to send twice, so they open again and retry once; a write is not, so it only drops the session. */
|
|
158
|
+
const request = async <M extends DatabaseMethod>(
|
|
159
|
+
method: M,
|
|
160
|
+
build: (session: string) => DatabaseParams<M>,
|
|
161
|
+
requestOptions: RequestOptions | undefined,
|
|
162
|
+
retry: boolean
|
|
163
|
+
): Promise<DatabaseResult<M>> => {
|
|
164
|
+
const signal = requestOptions?.signal;
|
|
165
|
+
|
|
166
|
+
if (signal?.aborted) {
|
|
167
|
+
throw cancelledError();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const current = await raceAbort(open(), signal);
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
return await send(method, build(current.session), signal);
|
|
174
|
+
} catch (error) {
|
|
175
|
+
if (!isUnknownSession(error)) {
|
|
176
|
+
throw error;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (opened === current) {
|
|
180
|
+
opened = null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (retry) {
|
|
184
|
+
return request(method, build, requestOptions, false);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
throw error;
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const close = async (): Promise<void> => {
|
|
192
|
+
const channels = live.get(connection.id);
|
|
193
|
+
|
|
194
|
+
if (channels?.get(channel) === created) {
|
|
195
|
+
channels.delete(channel);
|
|
196
|
+
|
|
197
|
+
if (channels.size === 0) {
|
|
198
|
+
live.delete(connection.id);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const current = opened ?? (opening === null ? null : await opening.catch(() => null));
|
|
203
|
+
opened = null;
|
|
204
|
+
|
|
205
|
+
if (current !== null) {
|
|
206
|
+
await send('close', { session: current.session }).catch(() => {});
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const session: DatabaseSession = {
|
|
211
|
+
get connection() {
|
|
212
|
+
return connection;
|
|
213
|
+
},
|
|
214
|
+
async server(requestOptions) {
|
|
215
|
+
return (await raceAbort(open(), requestOptions?.signal)).server;
|
|
216
|
+
},
|
|
217
|
+
async schemas(requestOptions) {
|
|
218
|
+
return (await request('schemas', (id) => ({ session: id }), requestOptions, true)).schemas;
|
|
219
|
+
},
|
|
220
|
+
async tables(schema, requestOptions) {
|
|
221
|
+
return (await request('tables', (id) => ({ session: id, schema }), requestOptions, true)).tables;
|
|
222
|
+
},
|
|
223
|
+
structure(schema, table, requestOptions) {
|
|
224
|
+
return request('structure', (id) => ({ session: id, schema, table }), requestOptions, true);
|
|
225
|
+
},
|
|
226
|
+
rows(schema, table, query, requestOptions) {
|
|
227
|
+
return request('rows', (id) => ({ session: id, schema, table, ...query }), requestOptions, true);
|
|
228
|
+
},
|
|
229
|
+
async count(schema, table, where, requestOptions) {
|
|
230
|
+
const params = (id: string) => (where === undefined ? { session: id, schema, table } : { session: id, schema, table, where });
|
|
231
|
+
return (await request('count', params, requestOptions, true)).count;
|
|
232
|
+
},
|
|
233
|
+
async cell(schema, table, key, column, requestOptions) {
|
|
234
|
+
return (await request('cell', (id) => ({ session: id, schema, table, key, column }), requestOptions, true)).value;
|
|
235
|
+
},
|
|
236
|
+
async apply(schema, table, changes, requestOptions) {
|
|
237
|
+
return (await request('apply', (id) => ({ session: id, schema, table, changes }), requestOptions, false)).affected;
|
|
238
|
+
},
|
|
239
|
+
async execute(sql, executeOptions?: ExecuteOptions) {
|
|
240
|
+
const { signal: _signal, ...rest } = executeOptions ?? {};
|
|
241
|
+
const result = await request('execute', (id) => ({ session: id, sql, ...rest }), executeOptions, false);
|
|
242
|
+
|
|
243
|
+
if (result.results.some((item) => item.kind !== 'error' && changesShape(item.sql))) {
|
|
244
|
+
notifySchemaChange({ connectionId: connection.id, ...(rest.schema === undefined ? {} : { schema: rest.schema }) });
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return result;
|
|
248
|
+
},
|
|
249
|
+
page(sql, query, requestOptions) {
|
|
250
|
+
return request('page', (id) => ({ session: id, sql, ...query }), requestOptions, true);
|
|
251
|
+
},
|
|
252
|
+
async transaction(action, requestOptions) {
|
|
253
|
+
return (await request('transaction', (id) => ({ session: id, action }), requestOptions, false)).active;
|
|
254
|
+
},
|
|
255
|
+
export(exportRequest, requestOptions) {
|
|
256
|
+
return request('export', (id) => ({ session: id, ...exportRequest }), requestOptions, false);
|
|
257
|
+
},
|
|
258
|
+
async import(schema, table, importRequest, requestOptions) {
|
|
259
|
+
return (await request('import', (id) => ({ session: id, schema, table, ...importRequest }), requestOptions, false)).rows;
|
|
260
|
+
},
|
|
261
|
+
close
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const created = {
|
|
265
|
+
session,
|
|
266
|
+
close,
|
|
267
|
+
update(next: Connection): void {
|
|
268
|
+
connection = next;
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
return created;
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
test(config: ConnectionConfig, requestOptions?: RequestOptions) {
|
|
276
|
+
return send('test', { connection: config }, requestOptions?.signal).then((result) => result.server);
|
|
277
|
+
},
|
|
278
|
+
async discover(kind, discoverOptions) {
|
|
279
|
+
const params = discoverOptions?.context === undefined ? { kind } : { kind, context: discoverOptions.context };
|
|
280
|
+
return (await send('discover', params, discoverOptions?.signal)).containers;
|
|
281
|
+
},
|
|
282
|
+
sample(path, format, header, requestOptions) {
|
|
283
|
+
return send('sample', { path, format, header }, requestOptions?.signal);
|
|
284
|
+
},
|
|
285
|
+
notifySchemaChange,
|
|
286
|
+
onSchemaChange(listener) {
|
|
287
|
+
schemaListeners.add(listener);
|
|
288
|
+
return () => {
|
|
289
|
+
schemaListeners.delete(listener);
|
|
290
|
+
};
|
|
291
|
+
},
|
|
292
|
+
session(connection, channel = '') {
|
|
293
|
+
let channels = live.get(connection.id);
|
|
294
|
+
const existing = channels?.get(channel);
|
|
295
|
+
|
|
296
|
+
if (existing !== undefined && isEqual(existing.session.connection.config, connection.config)) {
|
|
297
|
+
existing.update(connection);
|
|
298
|
+
return existing.session;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
void existing?.close();
|
|
302
|
+
|
|
303
|
+
const created = createSession(connection, channel);
|
|
304
|
+
|
|
305
|
+
if (channels === undefined) {
|
|
306
|
+
channels = new Map();
|
|
307
|
+
live.set(connection.id, channels);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
channels.set(channel, created);
|
|
311
|
+
return created.session;
|
|
312
|
+
},
|
|
313
|
+
async disconnect(connectionId) {
|
|
314
|
+
const existing = [...(live.get(connectionId)?.values() ?? [])];
|
|
315
|
+
live.delete(connectionId);
|
|
316
|
+
await Promise.all(existing.map((entry) => entry.close()));
|
|
317
|
+
},
|
|
318
|
+
async dispose() {
|
|
319
|
+
const all = [...live.values()].flatMap((channels) => [...channels.values()]);
|
|
320
|
+
live.clear();
|
|
321
|
+
await Promise.all(all.map((entry) => entry.close()));
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export { createDatabaseClient, type DatabaseClientOptions } from './client.ts';
|
|
2
|
+
export {
|
|
3
|
+
DatabaseRequestError,
|
|
4
|
+
type Connection,
|
|
5
|
+
type DatabaseClient,
|
|
6
|
+
type DatabaseSession,
|
|
7
|
+
type DatabaseTransport,
|
|
8
|
+
type ExecuteOptions,
|
|
9
|
+
type ExecuteResult,
|
|
10
|
+
type ExportRequest,
|
|
11
|
+
type ExportResult,
|
|
12
|
+
type ImportRequest,
|
|
13
|
+
type PageQuery,
|
|
14
|
+
type RequestOptions,
|
|
15
|
+
type RowsQuery,
|
|
16
|
+
type SchemaChange,
|
|
17
|
+
type TableRef
|
|
18
|
+
} from './types.ts';
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ConnectionConfig,
|
|
3
|
+
DatabaseErrorCode,
|
|
4
|
+
DockerContainer,
|
|
5
|
+
ExportSource,
|
|
6
|
+
FileFormat,
|
|
7
|
+
DatabaseRequest,
|
|
8
|
+
DatabaseResponse,
|
|
9
|
+
RowChange,
|
|
10
|
+
RowKey,
|
|
11
|
+
RowsResult,
|
|
12
|
+
SchemaInfo,
|
|
13
|
+
ServerInfo,
|
|
14
|
+
StatementResult,
|
|
15
|
+
TableInfo,
|
|
16
|
+
TableStructure,
|
|
17
|
+
Value
|
|
18
|
+
} from '../protocol/index.ts';
|
|
19
|
+
|
|
20
|
+
/* How the page reaches the host: the app carries the request over its own channel and hands back what the host answered. */
|
|
21
|
+
export type DatabaseTransport = (request: DatabaseRequest) => Promise<DatabaseResponse>;
|
|
22
|
+
|
|
23
|
+
/* A connection a person saved, as the manager lists it and the app stores it. */
|
|
24
|
+
export interface Connection {
|
|
25
|
+
/* Stable across edits, so the client keeps one session per connection. */
|
|
26
|
+
readonly id: string;
|
|
27
|
+
readonly name: string;
|
|
28
|
+
readonly config: ConnectionConfig;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* A table or a view in a connection. */
|
|
32
|
+
export interface TableRef {
|
|
33
|
+
readonly connectionId: string;
|
|
34
|
+
readonly schema: string;
|
|
35
|
+
readonly table: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface RequestOptions {
|
|
39
|
+
/* Aborting sends a `cancel` for the request, which then rejects with code `cancelled`. */
|
|
40
|
+
readonly signal?: AbortSignal;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface RowsQuery {
|
|
44
|
+
readonly where?: string;
|
|
45
|
+
readonly orderBy?: string;
|
|
46
|
+
readonly offset: number;
|
|
47
|
+
readonly limit: number;
|
|
48
|
+
readonly cellLimit?: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface ExecuteOptions extends RequestOptions {
|
|
52
|
+
readonly schema?: string;
|
|
53
|
+
readonly limit?: number;
|
|
54
|
+
readonly cellLimit?: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/*
|
|
58
|
+
* One open connection. It opens on its first request, and opens again when the host lost it (a helper
|
|
59
|
+
* that exited); a read is then sent once more, a write or an `execute` fails with `unknown-session`,
|
|
60
|
+
* since nobody can tell whether it ran.
|
|
61
|
+
*/
|
|
62
|
+
export interface DatabaseSession {
|
|
63
|
+
readonly connection: Connection;
|
|
64
|
+
server(options?: RequestOptions): Promise<ServerInfo>;
|
|
65
|
+
schemas(options?: RequestOptions): Promise<readonly SchemaInfo[]>;
|
|
66
|
+
tables(schema: string, options?: RequestOptions): Promise<readonly TableInfo[]>;
|
|
67
|
+
structure(schema: string, table: string, options?: RequestOptions): Promise<TableStructure>;
|
|
68
|
+
rows(schema: string, table: string, query: RowsQuery, options?: RequestOptions): Promise<RowsResult>;
|
|
69
|
+
count(schema: string, table: string, where?: string, options?: RequestOptions): Promise<number>;
|
|
70
|
+
cell(schema: string, table: string, key: RowKey, column: string, options?: RequestOptions): Promise<Value>;
|
|
71
|
+
apply(schema: string, table: string, changes: readonly RowChange[], options?: RequestOptions): Promise<number>;
|
|
72
|
+
execute(sql: string, options?: ExecuteOptions): Promise<ExecuteResult>;
|
|
73
|
+
page(sql: string, query: PageQuery, options?: RequestOptions): Promise<RowsResult>;
|
|
74
|
+
/* Resolves whether a transaction is open after the action. */
|
|
75
|
+
transaction(action: 'begin' | 'commit' | 'rollback', options?: RequestOptions): Promise<boolean>;
|
|
76
|
+
export(request: ExportRequest, options?: RequestOptions): Promise<ExportResult>;
|
|
77
|
+
import(schema: string, table: string, request: ImportRequest, options?: RequestOptions): Promise<number>;
|
|
78
|
+
close(): Promise<void>;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface ExecuteResult {
|
|
82
|
+
readonly results: readonly StatementResult[];
|
|
83
|
+
readonly inTransaction: boolean;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface PageQuery {
|
|
87
|
+
readonly schema?: string;
|
|
88
|
+
readonly offset: number;
|
|
89
|
+
readonly limit: number;
|
|
90
|
+
readonly cellLimit?: number;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface ExportRequest {
|
|
94
|
+
readonly source: ExportSource;
|
|
95
|
+
readonly format: FileFormat;
|
|
96
|
+
readonly path: string;
|
|
97
|
+
readonly header?: boolean;
|
|
98
|
+
readonly tableName?: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface ExportResult {
|
|
102
|
+
readonly rows: number;
|
|
103
|
+
readonly bytes: number;
|
|
104
|
+
readonly elapsedMs: number;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface ImportRequest {
|
|
108
|
+
readonly path: string;
|
|
109
|
+
readonly format: 'csv' | 'tsv';
|
|
110
|
+
readonly header: boolean;
|
|
111
|
+
readonly columns: readonly (string | null)[];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/* What changed in the shape of a database, so a tree or a structure view that shows it loads it again. */
|
|
115
|
+
export interface SchemaChange {
|
|
116
|
+
readonly connectionId: string;
|
|
117
|
+
/* `undefined` when the statement did not say which schema, which reloads every schema of the connection. */
|
|
118
|
+
readonly schema?: string;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface DatabaseClient {
|
|
122
|
+
test(config: ConnectionConfig, options?: RequestOptions): Promise<ServerInfo>;
|
|
123
|
+
discover(kind: 'docker', options?: RequestOptions & { readonly context?: string }): Promise<readonly DockerContainer[]>;
|
|
124
|
+
sample(
|
|
125
|
+
path: string,
|
|
126
|
+
format: 'csv' | 'tsv',
|
|
127
|
+
header: boolean,
|
|
128
|
+
options?: RequestOptions
|
|
129
|
+
): Promise<{ readonly columns: readonly string[]; readonly rows: readonly (readonly string[])[] }>;
|
|
130
|
+
/*
|
|
131
|
+
* Tells every listener the shape of a database changed. The client calls it itself after an `execute`
|
|
132
|
+
* that ran `CREATE`, `ALTER`, `DROP`, `RENAME` or `TRUNCATE`; a view that changes the shape another way calls it too.
|
|
133
|
+
*/
|
|
134
|
+
notifySchemaChange(change: SchemaChange): void;
|
|
135
|
+
/* Returns the function that stops listening. */
|
|
136
|
+
onSchemaChange(listener: (change: SchemaChange) => void): () => void;
|
|
137
|
+
/*
|
|
138
|
+
* The session of this connection on a channel, the same object on every call. Each channel is its own
|
|
139
|
+
* session on the host, so a transaction on one does not reach the others. A connection whose config
|
|
140
|
+
* changed gets a new one, and the old one closes.
|
|
141
|
+
*/
|
|
142
|
+
session(connection: Connection, channel?: string): DatabaseSession;
|
|
143
|
+
/* Closes every channel of the connection. */
|
|
144
|
+
disconnect(connectionId: string): Promise<void>;
|
|
145
|
+
dispose(): Promise<void>;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/* A request that failed, with the code of the protocol. */
|
|
149
|
+
export class DatabaseRequestError extends Error {
|
|
150
|
+
readonly code: DatabaseErrorCode;
|
|
151
|
+
readonly sqlState: string | undefined;
|
|
152
|
+
readonly change: number | undefined;
|
|
153
|
+
|
|
154
|
+
constructor(code: DatabaseErrorCode, message: string, sqlState?: string, change?: number) {
|
|
155
|
+
super(message);
|
|
156
|
+
this.name = 'DatabaseRequestError';
|
|
157
|
+
this.code = code;
|
|
158
|
+
this.sqlState = sqlState;
|
|
159
|
+
this.change = change;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { createContext, use } from 'react';
|
|
2
|
+
import type { DatabaseAction, DatabaseFiles, DatabaseStorage } from './actions.ts';
|
|
3
|
+
import type { DatabaseClient } from './client/types.ts';
|
|
4
|
+
import type { NumberNotation } from './grid/display.ts';
|
|
5
|
+
|
|
6
|
+
export interface DatabaseContextValue {
|
|
7
|
+
readonly client: DatabaseClient;
|
|
8
|
+
readonly onAction: ((action: DatabaseAction) => void) | undefined;
|
|
9
|
+
readonly storage: DatabaseStorage | undefined;
|
|
10
|
+
readonly files: DatabaseFiles | undefined;
|
|
11
|
+
readonly numberNotation: NumberNotation;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const DatabaseContext = createContext<DatabaseContextValue | null>(null);
|
|
15
|
+
|
|
16
|
+
const useDatabaseContext = (): DatabaseContextValue => {
|
|
17
|
+
const value = use(DatabaseContext);
|
|
18
|
+
if (value === null) {
|
|
19
|
+
throw new Error('A database view needs a DatabaseProvider above it.');
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const useDatabaseClient = (): DatabaseClient => useDatabaseContext().client;
|
|
25
|
+
|
|
26
|
+
/* Hands an action to the app, or `undefined` when the app takes none, so a view can leave out what would go nowhere. */
|
|
27
|
+
export const useDatabaseAction = (): ((action: DatabaseAction) => void) | undefined => useDatabaseContext().onAction;
|
|
28
|
+
|
|
29
|
+
export const useDatabaseStorage = (): DatabaseStorage | undefined => useDatabaseContext().storage;
|
|
30
|
+
|
|
31
|
+
/* The app's file dialogs; export and import stay hidden without them. */
|
|
32
|
+
export const useDatabaseFiles = (): DatabaseFiles | undefined => useDatabaseContext().files;
|
|
33
|
+
|
|
34
|
+
/* Read without the guard of the other hooks: a grid drawn on its own, such as in a test, writes numbers as the database does. */
|
|
35
|
+
export const useNumberNotation = (): NumberNotation => use(DatabaseContext)?.numberNotation ?? 'database';
|