@adecore/database 0.39.0 → 0.40.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 (131) hide show
  1. package/dist/console/QueryConsole.js +2 -10
  2. package/dist/console/SchemaPicker.js +0 -1
  3. package/dist/console/StatementResultView.js +1 -1
  4. package/dist/ddl/diff.js +1 -1
  5. package/dist/explorer/tree.js +0 -1
  6. package/dist/export-formats.d.ts +2 -0
  7. package/dist/export-formats.js +1 -0
  8. package/dist/grid/CopyAsMenu.js +0 -1
  9. package/dist/grid/DataGrid.js +10 -10
  10. package/dist/grid/column-layout.d.ts +0 -1
  11. package/dist/grid/column-layout.js +1 -2
  12. package/dist/grid/column-view.js +3 -2
  13. package/dist/grid/display.js +1 -2
  14. package/dist/host/host.js +1 -2
  15. package/dist/host/validate.d.ts +3 -0
  16. package/dist/host/validate.js +18 -15
  17. package/dist/same-list.d.ts +1 -0
  18. package/dist/same-list.js +1 -0
  19. package/dist/stored-size.d.ts +1 -0
  20. package/dist/stored-size.js +10 -0
  21. package/dist/table/TableToolbar.js +1 -1
  22. package/dist/table/TableView.js +2 -10
  23. package/package.json +15 -15
  24. package/src/DatabaseProvider.tsx +2 -0
  25. package/src/KeyIcon.tsx +1 -0
  26. package/src/client/client.ts +13 -0
  27. package/src/client-context.ts +2 -0
  28. package/src/connections/ConnectionForm.tsx +11 -0
  29. package/src/connections/ConnectionManager.tsx +9 -0
  30. package/src/connections/connection-config.ts +21 -0
  31. package/src/connections/docker.ts +2 -0
  32. package/src/connections/use-docker-containers.ts +3 -0
  33. package/src/console/DestructiveDialog.tsx +1 -0
  34. package/src/console/QueryConsole.tsx +38 -10
  35. package/src/console/SchemaPicker.tsx +1 -1
  36. package/src/console/SqlEditor.tsx +10 -0
  37. package/src/console/StatementResultView.tsx +7 -2
  38. package/src/console/TransactionControls.tsx +1 -0
  39. package/src/console/destructive.ts +11 -0
  40. package/src/console/editing.ts +10 -0
  41. package/src/console/history.ts +8 -0
  42. package/src/console/labels.ts +1 -0
  43. package/src/console/outcome.ts +1 -0
  44. package/src/console/result-keys.ts +16 -0
  45. package/src/console/shortcuts.ts +2 -0
  46. package/src/ddl/alter-mysql.ts +16 -0
  47. package/src/ddl/alter-sqlite.ts +21 -0
  48. package/src/ddl/alter.ts +2 -0
  49. package/src/ddl/column.ts +11 -0
  50. package/src/ddl/create.ts +15 -0
  51. package/src/ddl/dialect.ts +4 -0
  52. package/src/ddl/diff.ts +22 -2
  53. package/src/ddl/draft.ts +1 -0
  54. package/src/ddl/parse.ts +31 -0
  55. package/src/ddl/types.ts +3 -0
  56. package/src/ddl/validate.ts +12 -0
  57. package/src/designer/ColumnsEditor.tsx +3 -0
  58. package/src/designer/DesignerView.tsx +2 -0
  59. package/src/designer/ForeignKeysEditor.tsx +4 -0
  60. package/src/designer/IndexesEditor.tsx +1 -0
  61. package/src/designer/NamePicker.tsx +2 -0
  62. package/src/designer/OptionsEditor.tsx +4 -0
  63. package/src/designer/SqlPreview.tsx +2 -0
  64. package/src/designer/TableDesigner.tsx +14 -0
  65. package/src/designer/edit.ts +13 -0
  66. package/src/designer/plan.ts +3 -0
  67. package/src/designer/recover.ts +1 -0
  68. package/src/designer/useReferenceColumns.ts +5 -0
  69. package/src/explorer/DatabaseExplorer.tsx +40 -0
  70. package/src/explorer/RowMenu.tsx +2 -0
  71. package/src/explorer/TableDialog.tsx +6 -0
  72. package/src/explorer/expansion.ts +8 -0
  73. package/src/explorer/tree.ts +73 -2
  74. package/src/explorer/use-explorer-loads.ts +15 -0
  75. package/src/explorer/use-stored-expansion.ts +4 -0
  76. package/src/export-formats.ts +3 -0
  77. package/src/grid/CellEditor.tsx +1 -0
  78. package/src/grid/CopyAsMenu.tsx +1 -1
  79. package/src/grid/DataGrid.tsx +93 -23
  80. package/src/grid/EnumPicker.tsx +6 -0
  81. package/src/grid/GridHeaderCell.tsx +11 -0
  82. package/src/grid/GridRowView.tsx +3 -0
  83. package/src/grid/RecordView.tsx +19 -0
  84. package/src/grid/ValueDock.tsx +2 -0
  85. package/src/grid/aggregates.ts +12 -0
  86. package/src/grid/column-layout.ts +12 -4
  87. package/src/grid/column-selection.ts +9 -0
  88. package/src/grid/column-view.ts +6 -2
  89. package/src/grid/copy-formats.ts +13 -0
  90. package/src/grid/display.ts +17 -3
  91. package/src/grid/edit-value.ts +11 -0
  92. package/src/grid/enum-type.ts +15 -0
  93. package/src/grid/focused-value.ts +4 -0
  94. package/src/grid/layout.ts +12 -0
  95. package/src/grid/navigation.ts +5 -0
  96. package/src/grid/row-selection.ts +7 -0
  97. package/src/grid/sort.ts +1 -0
  98. package/src/grid/whole-cells.ts +8 -0
  99. package/src/host/host.ts +16 -3
  100. package/src/host/spawn.ts +2 -0
  101. package/src/host/validate.ts +37 -15
  102. package/src/same-list.ts +2 -0
  103. package/src/sql-split.ts +28 -0
  104. package/src/sql.ts +20 -0
  105. package/src/stored-size.ts +10 -0
  106. package/src/structure/StructureView.tsx +18 -0
  107. package/src/table/CommandField.tsx +11 -0
  108. package/src/table/ImportDialog.tsx +4 -0
  109. package/src/table/TableStatusBar.tsx +1 -0
  110. package/src/table/TableToolbar.tsx +2 -2
  111. package/src/table/TableView.tsx +50 -10
  112. package/src/table/command-field.ts +40 -0
  113. package/src/table/grid-rows.ts +13 -0
  114. package/src/table/import-mapping.ts +5 -0
  115. package/src/table/layout-store.ts +7 -0
  116. package/src/table/paging.ts +1 -0
  117. package/src/table/pending.ts +18 -0
  118. package/src/table/references.ts +4 -0
  119. package/src/table/useLoaded.ts +3 -0
  120. package/src/table/useStoredLayout.ts +2 -0
  121. package/src/table/useTableTransfer.ts +17 -1
  122. package/src/testing/fake.ts +24 -0
  123. package/src/use-database-resources.ts +1 -0
  124. package/src/use-popup-press.ts +3 -0
  125. package/src/value/ValueEditor.tsx +6 -0
  126. package/src/value/draft.ts +7 -0
  127. package/src/value/hex.ts +8 -0
  128. package/src/value/json.ts +16 -0
  129. package/src/value/views.ts +14 -0
  130. package/src/workbench/DatabaseWorkbench.tsx +22 -0
  131. package/src/workbench/tabs.ts +34 -0
@@ -8,16 +8,20 @@ export const wholeValueOf = (cell: Shown): Value | undefined =>
8
8
  /* What a cell holds as far as it goes: the whole value, or the preview of a long text or a binary value. */
9
9
  export const previewValueOf = (cell: Shown): Value => {
10
10
  const whole = wholeValueOf(cell);
11
+
11
12
  if (whole !== undefined) {
12
13
  return whole;
13
14
  }
15
+
14
16
  if (cell !== null && typeof cell === 'object') {
15
17
  if (cell.kind === 'longText') {
16
18
  return cell.preview;
17
19
  }
20
+
18
21
  if (cell.kind === 'binary') {
19
22
  return { kind: 'binary', hex: cell.hex };
20
23
  }
21
24
  }
25
+
22
26
  return null;
23
27
  };
@@ -3,14 +3,19 @@ export const ROW_HEIGHT = 28;
3
3
  export const HEADER_HEIGHT = 32;
4
4
  export const MIN_COLUMN_WIDTH = 64;
5
5
  export const MAX_COLUMN_WIDTH = 320;
6
+
6
7
  /* How far a drag may widen a column; the estimate stops at `MAX_COLUMN_WIDTH`, a person may go further. */
7
8
  export const MAX_RESIZED_WIDTH = 1200;
9
+
8
10
  /* How wide fitting a column to its content may make it. */
9
11
  export const MAX_FIT_WIDTH = 640;
12
+
10
13
  /* The window of drawn rows moves in blocks of this many rows, so a scroll within a block draws nothing. */
11
14
  export const ROW_BLOCK = 16;
15
+
12
16
  /* Two presses on a resize handle this close are a double click. */
13
17
  export const DOUBLE_CLICK_MS = 350;
18
+
14
19
  /* What a viewport is taken to be until it is measured, so a first paint already holds rows. */
15
20
  export const FALLBACK_VIEWPORT_HEIGHT = 600;
16
21
 
@@ -20,6 +25,7 @@ const CELL_PADDING = 24;
20
25
  const KEY_ICON_ROOM = 18;
21
26
  const GUTTER_MIN_WIDTH = 40;
22
27
  const SAMPLE_ROWS = 50;
28
+
23
29
  /* The arrow and the position number a sorted header carries after its name. */
24
30
  const SORT_ROOM = 24;
25
31
 
@@ -42,10 +48,12 @@ export const drawnRange = (block: number, height: number, count: number): Visibl
42
48
  if (count <= 0 || height <= 0) {
43
49
  return { start: 0, end: 0 };
44
50
  }
51
+
45
52
  const page = Math.ceil(height / ROW_HEIGHT);
46
53
  const top = Math.max(0, block) * ROW_BLOCK;
47
54
  const start = Math.max(0, Math.floor((top - page) / ROW_BLOCK) * ROW_BLOCK);
48
55
  const end = Math.min(count, Math.ceil((top + ROW_BLOCK + 2 * page) / ROW_BLOCK) * ROW_BLOCK);
56
+
49
57
  return { start: Math.min(start, end), end };
50
58
  };
51
59
 
@@ -57,18 +65,22 @@ export const scrollToReveal = (start: number, size: number, scroll: number, view
57
65
  if (start < scroll + leading) {
58
66
  return Math.max(0, start - leading);
59
67
  }
68
+
60
69
  if (start + size > scroll + viewport) {
61
70
  return Math.max(0, start + size - viewport);
62
71
  }
72
+
63
73
  return scroll;
64
74
  };
65
75
 
66
76
  /* The left edge of every column, counted from the first one. */
67
77
  export const columnOffsets = (widths: readonly number[]): number[] => {
68
78
  let offset = 0;
79
+
69
80
  return widths.map((width) => {
70
81
  const left = offset;
71
82
  offset += width;
83
+
72
84
  return left;
73
85
  });
74
86
  };
@@ -30,9 +30,11 @@ export const moveFocus = (from: CellPosition | null, input: NavigationKey, size:
30
30
  if (size.rows === 0 || size.columns === 0) {
31
31
  return null;
32
32
  }
33
+
33
34
  if (from === null && ENTERING_KEYS.has(input.key)) {
34
35
  return { row: 0, column: 0 };
35
36
  }
37
+
36
38
  const lastRow = size.rows - 1;
37
39
  const lastColumn = size.columns - 1;
38
40
  const start = from ?? { row: 0, column: 0 };
@@ -61,11 +63,14 @@ export const moveFocus = (from: CellPosition | null, input: NavigationKey, size:
61
63
  if (column > 0) {
62
64
  return { row, column: column - 1 };
63
65
  }
66
+
64
67
  return row > 0 ? { row: row - 1, column: lastColumn } : null;
65
68
  }
69
+
66
70
  if (column < lastColumn) {
67
71
  return { row, column: column + 1 };
68
72
  }
73
+
69
74
  return row < lastRow ? { row: row + 1, column: 0 } : null;
70
75
  }
71
76
  default:
@@ -15,21 +15,28 @@ export interface RowClick {
15
15
  */
16
16
  export const selectRow = (keys: readonly string[], current: ReadonlySet<string>, anchor: number, index: number, click: RowClick): RowSelection => {
17
17
  const key = keys[index];
18
+
18
19
  if (key === undefined) {
19
20
  return { selected: current, anchor };
20
21
  }
22
+
21
23
  if (click.shiftKey && anchor >= 0 && anchor < keys.length) {
22
24
  const from = Math.min(anchor, index);
23
25
  const to = Math.max(anchor, index);
24
26
  const range = keys.slice(from, to + 1);
27
+
25
28
  return { selected: new Set(click.mod ? [...current, ...range] : range), anchor };
26
29
  }
30
+
27
31
  if (click.mod) {
28
32
  const next = new Set(current);
33
+
29
34
  if (!next.delete(key)) {
30
35
  next.add(key);
31
36
  }
37
+
32
38
  return { selected: next, anchor: index };
33
39
  }
40
+
34
41
  return { selected: new Set([key]), anchor: index };
35
42
  };
package/src/grid/sort.ts CHANGED
@@ -8,5 +8,6 @@ export const sortOnly = (column: string, direction: SortDirection): GridSort[] =
8
8
  /* The sort of a column and its place among the sorts counted from one, or `null` when it is not sorted. */
9
9
  export const sortStateOf = (sorts: readonly GridSort[], column: string): { direction: SortDirection; position: number } | null => {
10
10
  const index = sorts.findIndex((sort) => sort.column === column);
11
+
11
12
  return index < 0 ? null : { direction: sorts[index]!.direction, position: index + 1 };
12
13
  };
@@ -24,6 +24,7 @@ const wholeSize = (cell: Shown): number => {
24
24
  if (cell === null || typeof cell !== 'object' || cell.kind === 'default') {
25
25
  return 0;
26
26
  }
27
+
27
28
  return cell.kind === 'longText' ? cell.length : cell.length * 2;
28
29
  };
29
30
 
@@ -37,6 +38,7 @@ export const loadWholeCells = async (
37
38
  ): Promise<WholeCells> => {
38
39
  const wanted: Wanted[] = [];
39
40
  let size = 0;
41
+
40
42
  rows.forEach((cells, row) =>
41
43
  cells.forEach((cell, column) => {
42
44
  if (isPreview(cell)) {
@@ -45,9 +47,11 @@ export const loadWholeCells = async (
45
47
  }
46
48
  })
47
49
  );
50
+
48
51
  if (wanted.length === 0) {
49
52
  return { rows, previews: 0, tooLarge: false };
50
53
  }
54
+
51
55
  if (load === undefined || size > WHOLE_COPY_LIMIT) {
52
56
  return { rows, previews: wanted.length, tooLarge: load !== undefined };
53
57
  }
@@ -55,11 +59,13 @@ export const loadWholeCells = async (
55
59
  const filled = rows.map((cells) => [...cells]);
56
60
  let previews = 0;
57
61
  let next = 0;
62
+
58
63
  const work = async (): Promise<void> => {
59
64
  while (next < wanted.length) {
60
65
  const { row, column } = wanted[next]!;
61
66
  next += 1;
62
67
  const value = await load(row, column).catch(() => undefined);
68
+
63
69
  if (value === undefined) {
64
70
  previews += 1;
65
71
  } else {
@@ -67,6 +73,8 @@ export const loadWholeCells = async (
67
73
  }
68
74
  }
69
75
  };
76
+
70
77
  await Promise.all(Array.from({ length: Math.min(PARALLEL_LOADS, wanted.length) }, work));
78
+
71
79
  return { rows: filled, previews, tooLarge: false };
72
80
  };
package/src/host/host.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { PROTOCOL_VERSION, type DatabaseError, type DatabaseErrorCode, type DatabaseRequest, type DatabaseResponse } from '../protocol/index.ts';
2
2
  import type { DatabaseHost, DatabaseHostOptions, HelperProcess } from './types.ts';
3
- import { parseRequest } from './validate.ts';
3
+ import { isDict, parseRequest } from './validate.ts';
4
4
 
5
5
  const DEFAULT_READY_TIMEOUT_MS = 10000;
6
6
 
@@ -49,11 +49,10 @@ const parseLine = (line: string): unknown => {
49
49
  }
50
50
  };
51
51
 
52
- const isDict = (value: unknown): value is Record<string, unknown> => typeof value === 'object' && value !== null && !Array.isArray(value);
53
-
54
52
  /* Settles when every promise has, or after the grace period; a helper that hangs must not hold up an app that quits. */
55
53
  const settleWithin = async (promises: readonly Promise<unknown>[]): Promise<void> => {
56
54
  let timer: ReturnType<typeof setTimeout> | undefined;
55
+
57
56
  const grace = new Promise<void>((resolve) => {
58
57
  timer = setTimeout(resolve, CLOSE_GRACE_MS);
59
58
  });
@@ -127,11 +126,13 @@ export const createDatabaseHost = (options: DatabaseHostOptions): DatabaseHost =
127
126
 
128
127
  if (!isDict(message) || message.event !== 'ready') {
129
128
  abort(run, 'The helper did not start with a ready line.');
129
+
130
130
  return;
131
131
  }
132
132
 
133
133
  if (message.protocol !== PROTOCOL_VERSION) {
134
134
  abort(run, `The helper speaks protocol ${String(message.protocol)}, this host speaks ${PROTOCOL_VERSION}.`);
135
+
135
136
  return;
136
137
  }
137
138
 
@@ -167,11 +168,14 @@ export const createDatabaseHost = (options: DatabaseHostOptions): DatabaseHost =
167
168
  }
168
169
 
169
170
  let resolveReady = (): void => {};
171
+
170
172
  let rejectReady = (_error: Failure): void => {};
173
+
171
174
  const ready = new Promise<void>((resolve, reject) => {
172
175
  resolveReady = resolve;
173
176
  rejectReady = reject;
174
177
  });
178
+
175
179
  // A request awaits this; a run nobody waits for must not raise an unhandled rejection.
176
180
  ready.catch(() => {});
177
181
 
@@ -185,6 +189,7 @@ export const createDatabaseHost = (options: DatabaseHostOptions): DatabaseHost =
185
189
  onResponse(run, line);
186
190
  }
187
191
  });
192
+
188
193
  child.onExit((code) => retire(run, code === null ? 'The helper exited.' : `The helper exited with code ${code}.`));
189
194
 
190
195
  return run;
@@ -208,6 +213,7 @@ export const createDatabaseHost = (options: DatabaseHostOptions): DatabaseHost =
208
213
  /* Sends a message under a helper id of the host's own; the promise never rejects. */
209
214
  const dispatch = (run: Run, method: string, params: unknown): { readonly id: string; readonly response: Promise<DatabaseResponse> } => {
210
215
  const id = `h${++counter}`;
216
+
211
217
  const response = new Promise<DatabaseResponse>((resolve) => {
212
218
  run.pending.set(id, resolve);
213
219
 
@@ -226,6 +232,7 @@ export const createDatabaseHost = (options: DatabaseHostOptions): DatabaseHost =
226
232
 
227
233
  const sessionOf = (request: DatabaseRequest): string | null => {
228
234
  const session = (request.params as { readonly session?: unknown }).session;
235
+
229
236
  return typeof session === 'string' ? session : null;
230
237
  };
231
238
 
@@ -292,6 +299,7 @@ export const createDatabaseHost = (options: DatabaseHostOptions): DatabaseHost =
292
299
  const cancelOwned = async (request: Extract<DatabaseRequest, { method: 'cancel' }>, owner: string): Promise<DatabaseResponse> => {
293
300
  const entries = inflight.get(owner);
294
301
  const target = request.id === request.params.request ? undefined : entries?.get(request.params.request);
302
+
295
303
  const answer = (cancelled: boolean): DatabaseResponse => ({ id: request.id, ok: true, result: { cancelled } });
296
304
 
297
305
  if (target === undefined) {
@@ -300,6 +308,7 @@ export const createDatabaseHost = (options: DatabaseHostOptions): DatabaseHost =
300
308
 
301
309
  if (target.helperId === null) {
302
310
  target.cancelled = true;
311
+
303
312
  return answer(true);
304
313
  }
305
314
 
@@ -310,6 +319,7 @@ export const createDatabaseHost = (options: DatabaseHostOptions): DatabaseHost =
310
319
  }
311
320
 
312
321
  const sent = await dispatch(run, 'cancel', { request: target.helperId }).response;
322
+
313
323
  return { ...sent, id: request.id } as DatabaseResponse;
314
324
  };
315
325
 
@@ -321,6 +331,7 @@ export const createDatabaseHost = (options: DatabaseHostOptions): DatabaseHost =
321
331
 
322
332
  // The helper ends a session whose connection to the server was lost.
323
333
  const target = sessionOf(request);
334
+
324
335
  if (target !== null && !response.ok && response.error.code === 'unknown-session') {
325
336
  sessions.delete(target);
326
337
  }
@@ -341,10 +352,12 @@ export const createDatabaseHost = (options: DatabaseHostOptions): DatabaseHost =
341
352
 
342
353
  if (entry.released) {
343
354
  void dispatch(run, 'close', { session }).response;
355
+
344
356
  return failure(request.id, 'cancelled', 'The request was cancelled.');
345
357
  }
346
358
 
347
359
  sessions.set(session, owner);
360
+
348
361
  return { ...response, id: request.id } as DatabaseResponse;
349
362
  };
350
363
 
package/src/host/spawn.ts CHANGED
@@ -95,6 +95,7 @@ export const spawnHelper = (path: string, options: SpawnHelperOptions = {}): Hel
95
95
  finish(null);
96
96
  }
97
97
  });
98
+
98
99
  child.on('close', (code, signal) => finish(code, signal));
99
100
 
100
101
  return {
@@ -109,6 +110,7 @@ export const spawnHelper = (path: string, options: SpawnHelperOptions = {}): Hel
109
110
  onExit(listener): void {
110
111
  if (finished) {
111
112
  queueMicrotask(() => listener(exitCode, exitSignal));
113
+
112
114
  return;
113
115
  }
114
116
 
@@ -23,7 +23,7 @@ const fail = (message: string): never => {
23
23
  throw new InvalidRequest(message);
24
24
  };
25
25
 
26
- const isDict = (value: unknown): value is Dict => typeof value === 'object' && value !== null && !Array.isArray(value);
26
+ export const isDict = (value: unknown): value is Dict => typeof value === 'object' && value !== null && !Array.isArray(value);
27
27
 
28
28
  const isRequestId = (value: unknown): value is string => typeof value === 'string' && value.length > 0 && value.length <= MAX_ID_LENGTH;
29
29
 
@@ -103,6 +103,7 @@ const value = (input: unknown, label: string): void => {
103
103
  if (!Number.isFinite(input)) {
104
104
  fail(`"${label}" must be a finite number.`);
105
105
  }
106
+
106
107
  return;
107
108
  }
108
109
 
@@ -120,6 +121,7 @@ const value = (input: unknown, label: string): void => {
120
121
  const editValue = (input: unknown, label: string): void => {
121
122
  if (isDict(input) && input.kind === 'default') {
122
123
  shape(input, label, ['kind']);
124
+
123
125
  return;
124
126
  }
125
127
 
@@ -129,6 +131,7 @@ const editValue = (input: unknown, label: string): void => {
129
131
  const values = (input: unknown, label: string, check: (value: unknown, label: string) => void, atLeastOne: boolean): void => {
130
132
  if (!isDict(input)) {
131
133
  fail(`"${label}" must be an object.`);
134
+
132
135
  return;
133
136
  }
134
137
 
@@ -146,6 +149,7 @@ const values = (input: unknown, label: string, check: (value: unknown, label: st
146
149
  const tunnel = (input: unknown, label: string): void => {
147
150
  if (!isDict(input)) {
148
151
  fail(`"${label}" must be an object.`);
152
+
149
153
  return;
150
154
  }
151
155
 
@@ -155,6 +159,7 @@ const tunnel = (input: unknown, label: string): void => {
155
159
  optional(config, 'port', label, port);
156
160
  optional(config, 'user', label, filledText);
157
161
  optional(config, 'identityFile', label, filledText);
162
+
158
163
  return;
159
164
  }
160
165
 
@@ -163,6 +168,7 @@ const tunnel = (input: unknown, label: string): void => {
163
168
  filledText(config.container, `${label}.container`);
164
169
  optional(config, 'port', label, port);
165
170
  optional(config, 'context', label, filledText);
171
+
166
172
  return;
167
173
  }
168
174
 
@@ -172,6 +178,7 @@ const tunnel = (input: unknown, label: string): void => {
172
178
  const connection = (input: unknown, label: string): void => {
173
179
  if (!isDict(input)) {
174
180
  fail(`"${label}" must be an object.`);
181
+
175
182
  return;
176
183
  }
177
184
 
@@ -180,6 +187,7 @@ const connection = (input: unknown, label: string): void => {
180
187
  absolutePath(config.path, `${label}.path`);
181
188
  optional(config, 'create', label, flag);
182
189
  optional(config, 'readOnly', label, flag);
190
+
183
191
  return;
184
192
  }
185
193
 
@@ -197,6 +205,7 @@ const connection = (input: unknown, label: string): void => {
197
205
  optional(config, 'database', label, anyText);
198
206
  optional(config, 'tls', label, oneOf(TLS_MODES));
199
207
  optional(config, 'readOnly', label, flag);
208
+
200
209
  return;
201
210
  }
202
211
 
@@ -206,6 +215,7 @@ const connection = (input: unknown, label: string): void => {
206
215
  const rowChange = (input: unknown, label: string): void => {
207
216
  if (!isDict(input)) {
208
217
  fail(`"${label}" must be an object.`);
218
+
209
219
  return;
210
220
  }
211
221
 
@@ -227,6 +237,7 @@ const rowChange = (input: unknown, label: string): void => {
227
237
  const exportSource = (input: unknown, label: string): void => {
228
238
  if (!isDict(input)) {
229
239
  fail(`"${label}" must be an object.`);
240
+
230
241
  return;
231
242
  }
232
243
 
@@ -236,6 +247,7 @@ const exportSource = (input: unknown, label: string): void => {
236
247
  filledText(source.table, `${label}.table`);
237
248
  optional(source, 'where', label, anyText);
238
249
  optional(source, 'orderBy', label, anyText);
250
+
239
251
  return;
240
252
  }
241
253
 
@@ -243,6 +255,7 @@ const exportSource = (input: unknown, label: string): void => {
243
255
  const source = shape(input, label, ['kind', 'sql'], ['schema']);
244
256
  filledText(source.sql, `${label}.sql`);
245
257
  optional(source, 'schema', label, filledText);
258
+
246
259
  return;
247
260
  }
248
261
 
@@ -253,6 +266,19 @@ const cellLimit = (params: Dict): void => optional(params, 'cellLimit', 'params'
253
266
 
254
267
  const maxBytes = (params: Dict): void => optional(params, 'maxBytes', 'params', (limit, name) => integer(limit, name, 1, MAX_BYTES));
255
268
 
269
+ const rowLimit = (params: Dict): void => optional(params, 'limit', 'params', (limit, name) => integer(limit, name, 1, MAX_LIMIT));
270
+
271
+ const pageWindow = (params: Dict): void => {
272
+ integer(params.offset, 'params.offset', 0, Number.MAX_SAFE_INTEGER);
273
+ integer(params.limit, 'params.limit', 1, MAX_LIMIT);
274
+ cellLimit(params);
275
+ maxBytes(params);
276
+ };
277
+
278
+ const connectionParams = (input: unknown): void => connection(shape(input, 'params', ['connection']).connection, 'params.connection');
279
+
280
+ const sessionParams = (input: unknown): void => filledText(shape(input, 'params', ['session']).session, 'params.session');
281
+
256
282
  const table = (params: Dict): void => {
257
283
  filledText(params.session, 'params.session');
258
284
  filledText(params.schema, 'params.schema');
@@ -262,10 +288,10 @@ const table = (params: Dict): void => {
262
288
  const TARGET = ['session', 'schema', 'table'];
263
289
 
264
290
  const PARAMS: { readonly [M in DatabaseMethod]: (input: unknown) => void } = {
265
- open: (input) => connection(shape(input, 'params', ['connection']).connection, 'params.connection'),
266
- test: (input) => connection(shape(input, 'params', ['connection']).connection, 'params.connection'),
267
- close: (input) => filledText(shape(input, 'params', ['session']).session, 'params.session'),
268
- schemas: (input) => filledText(shape(input, 'params', ['session']).session, 'params.session'),
291
+ open: connectionParams,
292
+ test: connectionParams,
293
+ close: sessionParams,
294
+ schemas: sessionParams,
269
295
  tables: (input) => {
270
296
  const params = shape(input, 'params', ['session', 'schema']);
271
297
  filledText(params.session, 'params.session');
@@ -277,10 +303,7 @@ const PARAMS: { readonly [M in DatabaseMethod]: (input: unknown) => void } = {
277
303
  table(params);
278
304
  optional(params, 'where', 'params', anyText);
279
305
  optional(params, 'orderBy', 'params', anyText);
280
- integer(params.offset, 'params.offset', 0, Number.MAX_SAFE_INTEGER);
281
- integer(params.limit, 'params.limit', 1, MAX_LIMIT);
282
- cellLimit(params);
283
- maxBytes(params);
306
+ pageWindow(params);
284
307
  },
285
308
  count: (input) => {
286
309
  const params = shape(input, 'params', TARGET, ['where']);
@@ -299,6 +322,7 @@ const PARAMS: { readonly [M in DatabaseMethod]: (input: unknown) => void } = {
299
322
 
300
323
  if (!Array.isArray(params.changes)) {
301
324
  fail('"params.changes" must be a list.');
325
+
302
326
  return;
303
327
  }
304
328
 
@@ -309,7 +333,7 @@ const PARAMS: { readonly [M in DatabaseMethod]: (input: unknown) => void } = {
309
333
  filledText(params.session, 'params.session');
310
334
  filledText(params.sql, 'params.sql');
311
335
  optional(params, 'schema', 'params', filledText);
312
- optional(params, 'limit', 'params', (limit, name) => integer(limit, name, 1, MAX_LIMIT));
336
+ rowLimit(params);
313
337
  cellLimit(params);
314
338
  maxBytes(params);
315
339
  },
@@ -318,10 +342,7 @@ const PARAMS: { readonly [M in DatabaseMethod]: (input: unknown) => void } = {
318
342
  filledText(params.session, 'params.session');
319
343
  filledText(params.sql, 'params.sql');
320
344
  optional(params, 'schema', 'params', filledText);
321
- integer(params.offset, 'params.offset', 0, Number.MAX_SAFE_INTEGER);
322
- integer(params.limit, 'params.limit', 1, MAX_LIMIT);
323
- cellLimit(params);
324
- maxBytes(params);
345
+ pageWindow(params);
325
346
  },
326
347
  transaction: (input) => {
327
348
  const params = shape(input, 'params', ['session', 'action']);
@@ -342,7 +363,7 @@ const PARAMS: { readonly [M in DatabaseMethod]: (input: unknown) => void } = {
342
363
  absolutePath(params.path, 'params.path');
343
364
  oneOf(DELIMITED_FORMATS)(params.format, 'params.format');
344
365
  flag(params.header, 'params.header');
345
- optional(params, 'limit', 'params', (limit, name) => integer(limit, name, 1, MAX_LIMIT));
366
+ rowLimit(params);
346
367
  },
347
368
  import: (input) => {
348
369
  const params = shape(input, 'params', [...TARGET, 'path', 'format', 'header', 'columns']);
@@ -353,6 +374,7 @@ const PARAMS: { readonly [M in DatabaseMethod]: (input: unknown) => void } = {
353
374
 
354
375
  if (!Array.isArray(params.columns)) {
355
376
  fail('"params.columns" must be a list.');
377
+
356
378
  return;
357
379
  }
358
380
 
@@ -0,0 +1,2 @@
1
+ export const sameList = (left: readonly string[], right: readonly string[]): boolean =>
2
+ left.length === right.length && left.every((item, index) => item === right[index]);