@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,343 @@
|
|
|
1
|
+
import { PROTOCOL_VERSION } from '../protocol/index.js';
|
|
2
|
+
import { parseRequest } from './validate.js';
|
|
3
|
+
const DEFAULT_READY_TIMEOUT_MS = 10000;
|
|
4
|
+
/* How long a release or a dispose waits for a helper to answer its `close` requests. */
|
|
5
|
+
const CLOSE_GRACE_MS = 2000;
|
|
6
|
+
/* A failure with the code the caller gets. */
|
|
7
|
+
class Failure extends Error {
|
|
8
|
+
code;
|
|
9
|
+
constructor(code, message) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.code = code;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const failure = (id, code, message) => ({ id, ok: false, error: { code, message } });
|
|
15
|
+
const messageOf = (error) => (error instanceof Error ? error.message : String(error));
|
|
16
|
+
const isDict = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
17
|
+
/* Settles when every promise has, or after the grace period; a helper that hangs must not hold up an app that quits. */
|
|
18
|
+
const settleWithin = async (promises) => {
|
|
19
|
+
let timer;
|
|
20
|
+
const grace = new Promise((resolve) => {
|
|
21
|
+
timer = setTimeout(resolve, CLOSE_GRACE_MS);
|
|
22
|
+
});
|
|
23
|
+
try {
|
|
24
|
+
await Promise.race([Promise.allSettled(promises), grace]);
|
|
25
|
+
}
|
|
26
|
+
finally {
|
|
27
|
+
clearTimeout(timer);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
/* Reads what the helper wrote for a request; anything that is not a response of the protocol becomes an `internal` error. */
|
|
31
|
+
const normalize = (id, message) => {
|
|
32
|
+
if (message.ok === true) {
|
|
33
|
+
return { id, ok: true, result: message.result ?? null };
|
|
34
|
+
}
|
|
35
|
+
const error = message.error;
|
|
36
|
+
if (message.ok === false && isDict(error) && typeof error.code === 'string' && typeof error.message === 'string') {
|
|
37
|
+
return { id, ok: false, error: error };
|
|
38
|
+
}
|
|
39
|
+
return failure(id, 'internal', 'The helper sent a response that is not part of the protocol.');
|
|
40
|
+
};
|
|
41
|
+
export const createDatabaseHost = (options) => {
|
|
42
|
+
const readyTimeoutMs = options.readyTimeoutMs ?? DEFAULT_READY_TIMEOUT_MS;
|
|
43
|
+
// Session id to the owner that opened it.
|
|
44
|
+
const sessions = new Map();
|
|
45
|
+
const inflight = new Map();
|
|
46
|
+
let running = null;
|
|
47
|
+
let counter = 0;
|
|
48
|
+
let disposed = false;
|
|
49
|
+
let disposal = null;
|
|
50
|
+
const retire = (run, message) => {
|
|
51
|
+
if (run.state === 'dead') {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const wasStarting = run.state === 'starting';
|
|
55
|
+
run.state = 'dead';
|
|
56
|
+
clearTimeout(run.timer);
|
|
57
|
+
if (running === run) {
|
|
58
|
+
running = null;
|
|
59
|
+
}
|
|
60
|
+
sessions.clear();
|
|
61
|
+
if (wasStarting) {
|
|
62
|
+
run.rejectReady(new Failure('helper-unavailable', message));
|
|
63
|
+
}
|
|
64
|
+
const pending = [...run.pending.entries()];
|
|
65
|
+
run.pending.clear();
|
|
66
|
+
for (const [id, resolve] of pending) {
|
|
67
|
+
resolve(failure(id, 'helper-exited', message));
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
const abort = (run, message) => {
|
|
71
|
+
run.process.kill();
|
|
72
|
+
retire(run, message);
|
|
73
|
+
};
|
|
74
|
+
const onHandshake = (run, line) => {
|
|
75
|
+
let message;
|
|
76
|
+
try {
|
|
77
|
+
message = JSON.parse(line);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
abort(run, 'The helper did not start with a ready line.');
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (!isDict(message) || message.event !== 'ready') {
|
|
84
|
+
abort(run, 'The helper did not start with a ready line.');
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (message.protocol !== PROTOCOL_VERSION) {
|
|
88
|
+
abort(run, `The helper speaks protocol ${String(message.protocol)}, this host speaks ${PROTOCOL_VERSION}.`);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
run.state = 'ready';
|
|
92
|
+
clearTimeout(run.timer);
|
|
93
|
+
run.resolveReady();
|
|
94
|
+
};
|
|
95
|
+
const onResponse = (run, line) => {
|
|
96
|
+
let message;
|
|
97
|
+
try {
|
|
98
|
+
message = JSON.parse(line);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (!isDict(message) || typeof message.id !== 'string') {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const resolve = run.pending.get(message.id);
|
|
107
|
+
if (resolve === undefined) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
run.pending.delete(message.id);
|
|
111
|
+
resolve(normalize(message.id, message));
|
|
112
|
+
};
|
|
113
|
+
const startRun = () => {
|
|
114
|
+
let child;
|
|
115
|
+
try {
|
|
116
|
+
child = options.start();
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
throw new Failure('helper-unavailable', `The helper could not be started: ${messageOf(error)}`);
|
|
120
|
+
}
|
|
121
|
+
let resolveReady = () => { };
|
|
122
|
+
let rejectReady = (_error) => { };
|
|
123
|
+
const ready = new Promise((resolve, reject) => {
|
|
124
|
+
resolveReady = resolve;
|
|
125
|
+
rejectReady = reject;
|
|
126
|
+
});
|
|
127
|
+
// A request awaits this; a run nobody waits for must not raise an unhandled rejection.
|
|
128
|
+
ready.catch(() => { });
|
|
129
|
+
const run = { process: child, ready, pending: new Map(), resolveReady, rejectReady, timer: undefined, state: 'starting' };
|
|
130
|
+
run.timer = setTimeout(() => abort(run, 'The helper did not become ready in time.'), readyTimeoutMs);
|
|
131
|
+
child.onLine((line) => {
|
|
132
|
+
if (run.state === 'starting') {
|
|
133
|
+
onHandshake(run, line);
|
|
134
|
+
}
|
|
135
|
+
else if (run.state === 'ready') {
|
|
136
|
+
onResponse(run, line);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
child.onExit((code) => retire(run, code === null ? 'The helper exited.' : `The helper exited with code ${code}.`));
|
|
140
|
+
return run;
|
|
141
|
+
};
|
|
142
|
+
const ensureRunning = async () => {
|
|
143
|
+
if (running === null || running.state === 'dead') {
|
|
144
|
+
running = startRun();
|
|
145
|
+
}
|
|
146
|
+
const run = running;
|
|
147
|
+
await run.ready;
|
|
148
|
+
if (run.state === 'dead') {
|
|
149
|
+
throw new Failure('helper-exited', 'The helper exited.');
|
|
150
|
+
}
|
|
151
|
+
return run;
|
|
152
|
+
};
|
|
153
|
+
/* Sends a message under a helper id of the host's own; the promise never rejects. */
|
|
154
|
+
const dispatch = (run, method, params) => {
|
|
155
|
+
const id = `h${++counter}`;
|
|
156
|
+
const response = new Promise((resolve) => {
|
|
157
|
+
run.pending.set(id, resolve);
|
|
158
|
+
try {
|
|
159
|
+
run.process.write(JSON.stringify({ id, method, params }));
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
run.pending.delete(id);
|
|
163
|
+
resolve(failure(id, 'helper-unavailable', `The helper could not be written to: ${messageOf(error)}`));
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
return { id, response };
|
|
167
|
+
};
|
|
168
|
+
const liveRun = () => (running !== null && running.state === 'ready' ? running : null);
|
|
169
|
+
const sessionOf = (request) => {
|
|
170
|
+
const session = request.params.session;
|
|
171
|
+
return typeof session === 'string' ? session : null;
|
|
172
|
+
};
|
|
173
|
+
const assertSession = (request, owner) => {
|
|
174
|
+
const session = sessionOf(request);
|
|
175
|
+
if (session !== null && sessions.get(session) !== owner) {
|
|
176
|
+
throw new Failure('unknown-session', `There is no open session "${session}".`);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
const assertActive = (entry) => {
|
|
180
|
+
if (disposed) {
|
|
181
|
+
throw new Failure('helper-unavailable', 'The database host has been disposed.');
|
|
182
|
+
}
|
|
183
|
+
if (entry.cancelled) {
|
|
184
|
+
throw new Failure('cancelled', 'The request was cancelled.');
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
/* An app's check that throws or rejects counts as a no. */
|
|
188
|
+
const permitted = async (check) => {
|
|
189
|
+
try {
|
|
190
|
+
return await check();
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
const authorize = async (request, owner) => {
|
|
197
|
+
switch (request.method) {
|
|
198
|
+
case 'open':
|
|
199
|
+
case 'test': {
|
|
200
|
+
if (options.authorize !== undefined && !(await permitted(() => options.authorize(request.params.connection, owner)))) {
|
|
201
|
+
throw new Failure('forbidden', 'This connection is not allowed.');
|
|
202
|
+
}
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
case 'export':
|
|
206
|
+
case 'import':
|
|
207
|
+
case 'sample': {
|
|
208
|
+
const access = request.method === 'export' ? 'write' : 'read';
|
|
209
|
+
if (options.authorizeFile === undefined || !(await permitted(() => options.authorizeFile(request.params.path, access, owner)))) {
|
|
210
|
+
throw new Failure('forbidden', 'This file is not allowed.');
|
|
211
|
+
}
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
case 'discover': {
|
|
215
|
+
if (options.authorizeDiscovery === undefined || !(await permitted(() => options.authorizeDiscovery(request.params.kind, owner)))) {
|
|
216
|
+
throw new Failure('forbidden', 'Discovery is not allowed.');
|
|
217
|
+
}
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
const cancelOwned = async (request, owner) => {
|
|
223
|
+
const entries = inflight.get(owner);
|
|
224
|
+
const target = request.id === request.params.request ? undefined : entries?.get(request.params.request);
|
|
225
|
+
const answer = (cancelled) => ({ id: request.id, ok: true, result: { cancelled } });
|
|
226
|
+
if (target === undefined) {
|
|
227
|
+
return answer(false);
|
|
228
|
+
}
|
|
229
|
+
if (target.helperId === null) {
|
|
230
|
+
target.cancelled = true;
|
|
231
|
+
return answer(true);
|
|
232
|
+
}
|
|
233
|
+
const run = liveRun();
|
|
234
|
+
if (run === null) {
|
|
235
|
+
return answer(false);
|
|
236
|
+
}
|
|
237
|
+
const sent = await dispatch(run, 'cancel', { request: target.helperId }).response;
|
|
238
|
+
return { ...sent, id: request.id };
|
|
239
|
+
};
|
|
240
|
+
/* Remembers what a response changed about the sessions, then hands it back under the caller's id. */
|
|
241
|
+
const settle = (request, owner, entry, run, response) => {
|
|
242
|
+
if (request.method === 'close') {
|
|
243
|
+
sessions.delete(request.params.session);
|
|
244
|
+
}
|
|
245
|
+
if (request.method !== 'open' || !response.ok) {
|
|
246
|
+
return { ...response, id: request.id };
|
|
247
|
+
}
|
|
248
|
+
const session = response.result?.session;
|
|
249
|
+
if (typeof session !== 'string') {
|
|
250
|
+
return failure(request.id, 'internal', 'The helper opened a session without an id.');
|
|
251
|
+
}
|
|
252
|
+
if (run.state === 'dead') {
|
|
253
|
+
return failure(request.id, 'helper-exited', 'The helper exited.');
|
|
254
|
+
}
|
|
255
|
+
if (entry.released) {
|
|
256
|
+
void dispatch(run, 'close', { session }).response;
|
|
257
|
+
return failure(request.id, 'cancelled', 'The request was cancelled.');
|
|
258
|
+
}
|
|
259
|
+
sessions.set(session, owner);
|
|
260
|
+
return { ...response, id: request.id };
|
|
261
|
+
};
|
|
262
|
+
const route = async (request, owner, entry) => {
|
|
263
|
+
assertSession(request, owner);
|
|
264
|
+
if (request.method === 'cancel') {
|
|
265
|
+
return cancelOwned(request, owner);
|
|
266
|
+
}
|
|
267
|
+
await authorize(request, owner);
|
|
268
|
+
assertActive(entry);
|
|
269
|
+
const run = await ensureRunning();
|
|
270
|
+
assertActive(entry);
|
|
271
|
+
assertSession(request, owner);
|
|
272
|
+
const sent = dispatch(run, request.method, request.params);
|
|
273
|
+
entry.helperId = sent.id;
|
|
274
|
+
return settle(request, owner, entry, run, await sent.response);
|
|
275
|
+
};
|
|
276
|
+
const handle = async (input, owner) => {
|
|
277
|
+
const parsed = parseRequest(input);
|
|
278
|
+
if (!parsed.ok) {
|
|
279
|
+
return { id: parsed.id, ok: false, error: parsed.error };
|
|
280
|
+
}
|
|
281
|
+
const request = parsed.request;
|
|
282
|
+
if (disposed) {
|
|
283
|
+
return failure(request.id, 'helper-unavailable', 'The database host has been disposed.');
|
|
284
|
+
}
|
|
285
|
+
let entries = inflight.get(owner);
|
|
286
|
+
if (entries === undefined) {
|
|
287
|
+
entries = new Map();
|
|
288
|
+
inflight.set(owner, entries);
|
|
289
|
+
}
|
|
290
|
+
if (entries.has(request.id)) {
|
|
291
|
+
return failure(request.id, 'invalid-request', `A request with id "${request.id}" is already running.`);
|
|
292
|
+
}
|
|
293
|
+
const entry = { helperId: null, cancelled: false, released: false };
|
|
294
|
+
entries.set(request.id, entry);
|
|
295
|
+
try {
|
|
296
|
+
return await route(request, owner, entry);
|
|
297
|
+
}
|
|
298
|
+
catch (error) {
|
|
299
|
+
if (error instanceof Failure) {
|
|
300
|
+
return failure(request.id, error.code, error.message);
|
|
301
|
+
}
|
|
302
|
+
return failure(request.id, 'internal', messageOf(error));
|
|
303
|
+
}
|
|
304
|
+
finally {
|
|
305
|
+
entries.delete(request.id);
|
|
306
|
+
if (entries.size === 0 && inflight.get(owner) === entries) {
|
|
307
|
+
inflight.delete(owner);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
const release = async (owner) => {
|
|
312
|
+
const run = liveRun();
|
|
313
|
+
const closing = [];
|
|
314
|
+
for (const entry of inflight.get(owner)?.values() ?? []) {
|
|
315
|
+
entry.cancelled = true;
|
|
316
|
+
entry.released = true;
|
|
317
|
+
if (entry.helperId !== null && run !== null) {
|
|
318
|
+
closing.push(dispatch(run, 'cancel', { request: entry.helperId }).response);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
for (const [session, sessionOwner] of [...sessions]) {
|
|
322
|
+
if (sessionOwner !== owner) {
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
sessions.delete(session);
|
|
326
|
+
if (run !== null) {
|
|
327
|
+
closing.push(dispatch(run, 'close', { session }).response);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
await settleWithin(closing);
|
|
331
|
+
};
|
|
332
|
+
const dispose = () => {
|
|
333
|
+
disposal ??= (async () => {
|
|
334
|
+
disposed = true;
|
|
335
|
+
await Promise.all([...new Set([...inflight.keys(), ...sessions.values()])].map(release));
|
|
336
|
+
if (running !== null) {
|
|
337
|
+
abort(running, 'The database host has been disposed.');
|
|
338
|
+
}
|
|
339
|
+
})();
|
|
340
|
+
return disposal;
|
|
341
|
+
};
|
|
342
|
+
return { handle, release, dispose };
|
|
343
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { helperPath, type HelperPathOptions } from './helper-path.ts';
|
|
2
|
+
export { createDatabaseHost } from './host.ts';
|
|
3
|
+
export { spawnHelper, type SpawnHelperOptions } from './spawn.ts';
|
|
4
|
+
export type { DatabaseHost, DatabaseHostOptions, HelperProcess } from './types.ts';
|
|
5
|
+
export { parseRequest, type ParsedRequest } from './validate.ts';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { HelperProcess } from './types.ts';
|
|
2
|
+
export interface SpawnHelperOptions {
|
|
3
|
+
readonly args?: readonly string[];
|
|
4
|
+
readonly env?: Readonly<Record<string, string>>;
|
|
5
|
+
readonly onLog?: (line: string) => void;
|
|
6
|
+
}
|
|
7
|
+
export declare const spawnHelper: (path: string, options?: SpawnHelperOptions) => HelperProcess;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
/*
|
|
3
|
+
* Cuts a stream into lines. A line can be megabytes, so pieces wait in a list and only the new chunk is
|
|
4
|
+
* searched for a break; joining a growing string on every chunk would make a large line quadratic.
|
|
5
|
+
*/
|
|
6
|
+
const createLineSplitter = (onLine) => {
|
|
7
|
+
let pieces = [];
|
|
8
|
+
const emit = (line) => {
|
|
9
|
+
const clean = line.endsWith('\r') ? line.slice(0, -1) : line;
|
|
10
|
+
if (clean.length > 0) {
|
|
11
|
+
onLine(clean);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
return {
|
|
15
|
+
push(chunk) {
|
|
16
|
+
let start = 0;
|
|
17
|
+
let end = chunk.indexOf('\n');
|
|
18
|
+
while (end !== -1) {
|
|
19
|
+
pieces.push(chunk.slice(start, end));
|
|
20
|
+
const line = pieces.join('');
|
|
21
|
+
pieces = [];
|
|
22
|
+
emit(line);
|
|
23
|
+
start = end + 1;
|
|
24
|
+
end = chunk.indexOf('\n', start);
|
|
25
|
+
}
|
|
26
|
+
if (start < chunk.length) {
|
|
27
|
+
pieces.push(chunk.slice(start));
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
flush() {
|
|
31
|
+
const line = pieces.join('');
|
|
32
|
+
pieces = [];
|
|
33
|
+
emit(line);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
/* Runs the helper binary at `path`, speaking one JSON message per line over stdin and stdout. */
|
|
38
|
+
export const spawnHelper = (path, options = {}) => {
|
|
39
|
+
const lineListeners = [];
|
|
40
|
+
const exitListeners = [];
|
|
41
|
+
let finished = false;
|
|
42
|
+
let exitCode = null;
|
|
43
|
+
let killed = false;
|
|
44
|
+
const child = spawn(path, [...(options.args ?? [])], {
|
|
45
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
46
|
+
env: options.env ? { ...process.env, ...options.env } : process.env,
|
|
47
|
+
windowsHide: true
|
|
48
|
+
});
|
|
49
|
+
const lines = createLineSplitter((line) => lineListeners.forEach((listener) => listener(line)));
|
|
50
|
+
const logs = createLineSplitter((line) => options.onLog?.(line));
|
|
51
|
+
child.stdout.setEncoding('utf8');
|
|
52
|
+
child.stderr.setEncoding('utf8');
|
|
53
|
+
child.stdout.on('data', (chunk) => lines.push(chunk));
|
|
54
|
+
child.stderr.on('data', (chunk) => logs.push(chunk));
|
|
55
|
+
child.stdin.on('error', () => { });
|
|
56
|
+
const finish = (code) => {
|
|
57
|
+
if (finished) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
finished = true;
|
|
61
|
+
exitCode = code;
|
|
62
|
+
lines.flush();
|
|
63
|
+
logs.flush();
|
|
64
|
+
exitListeners.forEach((listener) => listener(code));
|
|
65
|
+
};
|
|
66
|
+
child.on('error', (error) => {
|
|
67
|
+
options.onLog?.(error.message);
|
|
68
|
+
// Without a pid the process never started, and no `close` follows.
|
|
69
|
+
if (child.pid === undefined) {
|
|
70
|
+
finish(null);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
child.on('close', (code) => finish(code));
|
|
74
|
+
return {
|
|
75
|
+
write(line) {
|
|
76
|
+
if (!finished && child.stdin.writable) {
|
|
77
|
+
child.stdin.write(`${line}\n`);
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
onLine(listener) {
|
|
81
|
+
lineListeners.push(listener);
|
|
82
|
+
},
|
|
83
|
+
onExit(listener) {
|
|
84
|
+
if (finished) {
|
|
85
|
+
queueMicrotask(() => listener(exitCode));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
exitListeners.push(listener);
|
|
89
|
+
},
|
|
90
|
+
kill() {
|
|
91
|
+
if (killed || finished) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
killed = true;
|
|
95
|
+
child.kill();
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ConnectionConfig, DatabaseResponse } from '../protocol/index.ts';
|
|
2
|
+
export interface HelperProcess {
|
|
3
|
+
write(line: string): void;
|
|
4
|
+
onLine(listener: (line: string) => void): void;
|
|
5
|
+
onExit(listener: (code: number | null) => void): void;
|
|
6
|
+
kill(): void;
|
|
7
|
+
}
|
|
8
|
+
export interface DatabaseHostOptions {
|
|
9
|
+
start(): HelperProcess;
|
|
10
|
+
authorize?(connection: ConnectionConfig, owner: string): boolean | Promise<boolean>;
|
|
11
|
+
authorizeFile?(path: string, access: 'read' | 'write', owner: string): boolean | Promise<boolean>;
|
|
12
|
+
authorizeDiscovery?(kind: 'docker', owner: string): boolean | Promise<boolean>;
|
|
13
|
+
readyTimeoutMs?: number;
|
|
14
|
+
}
|
|
15
|
+
export interface DatabaseHost {
|
|
16
|
+
handle(request: unknown, owner: string): Promise<DatabaseResponse>;
|
|
17
|
+
release(owner: string): Promise<void>;
|
|
18
|
+
dispose(): Promise<void>;
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { DatabaseError, DatabaseRequest } from '../protocol/index.ts';
|
|
2
|
+
export type ParsedRequest = {
|
|
3
|
+
readonly ok: true;
|
|
4
|
+
readonly request: DatabaseRequest;
|
|
5
|
+
} | {
|
|
6
|
+
readonly ok: false;
|
|
7
|
+
readonly id: string;
|
|
8
|
+
readonly error: DatabaseError;
|
|
9
|
+
};
|
|
10
|
+
export declare const parseRequest: (input: unknown) => ParsedRequest;
|