@adecore/database 0.21.0 → 0.21.2

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 (109) hide show
  1. package/dist/assign-ref.d.ts +2 -0
  2. package/dist/assign-ref.js +9 -0
  3. package/dist/client/client.js +14 -15
  4. package/dist/client/types.d.ts +1 -0
  5. package/dist/client/types.js +2 -0
  6. package/dist/connections/ConnectionForm.js +7 -2
  7. package/dist/connections/ConnectionManager.js +5 -5
  8. package/dist/connections/EngineIcon.js +0 -1
  9. package/dist/console/QueryConsole.js +12 -4
  10. package/dist/console/editing.js +2 -4
  11. package/dist/console/useConsoleHistory.js +3 -2
  12. package/dist/ddl/alter-mysql.js +2 -8
  13. package/dist/ddl/alter-sqlite.js +1 -1
  14. package/dist/ddl/column.js +5 -8
  15. package/dist/ddl/create.d.ts +1 -0
  16. package/dist/ddl/create.js +1 -1
  17. package/dist/ddl/diff.js +25 -23
  18. package/dist/designer/ColumnsEditor.js +8 -6
  19. package/dist/designer/ForeignKeysEditor.js +5 -1
  20. package/dist/designer/OptionsEditor.js +12 -3
  21. package/dist/designer/SqlPreview.d.ts +1 -5
  22. package/dist/designer/SqlPreview.js +1 -1
  23. package/dist/designer/TableDesigner.js +7 -6
  24. package/dist/explorer/DatabaseExplorer.js +12 -13
  25. package/dist/explorer/TableDialog.js +1 -1
  26. package/dist/explorer/tree.js +6 -16
  27. package/dist/grid/DataGrid.js +11 -14
  28. package/dist/grid/EnumPicker.js +4 -3
  29. package/dist/grid/GridHeaderCell.d.ts +1 -1
  30. package/dist/grid/GridHeaderCell.js +2 -2
  31. package/dist/grid/RecordView.js +2 -1
  32. package/dist/grid/ValueDock.js +2 -6
  33. package/dist/grid/aggregates.js +2 -3
  34. package/dist/grid/column-layout.js +3 -2
  35. package/dist/grid/copy-formats.js +2 -3
  36. package/dist/grid/display.d.ts +0 -1
  37. package/dist/grid/display.js +1 -3
  38. package/dist/grid/layout.js +6 -11
  39. package/dist/host/helper-path.js +8 -0
  40. package/dist/host/host.js +11 -16
  41. package/dist/host/validate.js +44 -43
  42. package/dist/sql.d.ts +2 -0
  43. package/dist/sql.js +5 -2
  44. package/dist/structure/StructureView.js +5 -6
  45. package/dist/table/TableView.js +19 -12
  46. package/dist/table/command-field.js +8 -12
  47. package/dist/table/grid-rows.js +2 -1
  48. package/dist/table/layout-store.js +5 -2
  49. package/dist/table/pending.js +11 -3
  50. package/dist/table/useTableTransfer.js +6 -5
  51. package/dist/testing/fake.js +7 -9
  52. package/dist/value/ValueEditor.js +1 -1
  53. package/dist/value/draft.js +1 -6
  54. package/dist/value/json.js +4 -4
  55. package/dist/workbench/DatabaseWorkbench.js +12 -5
  56. package/dist/workbench/tabs.js +3 -4
  57. package/package.json +6 -6
  58. package/src/assign-ref.ts +10 -0
  59. package/src/client/client.ts +17 -18
  60. package/src/client/types.ts +3 -0
  61. package/src/connections/ConnectionForm.tsx +62 -47
  62. package/src/connections/ConnectionManager.tsx +6 -5
  63. package/src/connections/EngineIcon.tsx +0 -2
  64. package/src/console/QueryConsole.tsx +52 -31
  65. package/src/console/editing.ts +2 -4
  66. package/src/console/useConsoleHistory.ts +5 -2
  67. package/src/ddl/alter-mysql.ts +3 -11
  68. package/src/ddl/alter-sqlite.ts +1 -1
  69. package/src/ddl/column.ts +5 -7
  70. package/src/ddl/create.ts +1 -1
  71. package/src/ddl/diff.ts +30 -25
  72. package/src/designer/ColumnsEditor.tsx +145 -125
  73. package/src/designer/ForeignKeysEditor.tsx +44 -27
  74. package/src/designer/OptionsEditor.tsx +95 -75
  75. package/src/designer/SqlPreview.tsx +2 -3
  76. package/src/designer/TableDesigner.tsx +7 -6
  77. package/src/explorer/DatabaseExplorer.tsx +26 -31
  78. package/src/explorer/TableDialog.tsx +1 -1
  79. package/src/explorer/tree.ts +8 -19
  80. package/src/grid/DataGrid.tsx +11 -14
  81. package/src/grid/EnumPicker.tsx +4 -3
  82. package/src/grid/GridHeaderCell.tsx +2 -3
  83. package/src/grid/RecordView.tsx +2 -1
  84. package/src/grid/ValueDock.tsx +2 -5
  85. package/src/grid/aggregates.ts +2 -4
  86. package/src/grid/column-layout.ts +3 -2
  87. package/src/grid/copy-formats.ts +2 -3
  88. package/src/grid/display.ts +1 -5
  89. package/src/grid/layout.ts +10 -11
  90. package/src/host/helper-path.ts +8 -8
  91. package/src/host/host.ts +11 -16
  92. package/src/host/validate.ts +45 -43
  93. package/src/protocol/messages.ts +0 -1
  94. package/src/sql.ts +7 -2
  95. package/src/structure/StructureView.tsx +7 -8
  96. package/src/table/CommandField.tsx +0 -1
  97. package/src/table/TableView.tsx +48 -34
  98. package/src/table/command-field.ts +9 -12
  99. package/src/table/grid-rows.ts +2 -7
  100. package/src/table/layout-store.ts +6 -3
  101. package/src/table/pending.ts +12 -7
  102. package/src/table/useTableTransfer.ts +7 -5
  103. package/src/testing/fake.ts +8 -12
  104. package/src/value/ValueEditor.tsx +1 -1
  105. package/src/value/draft.ts +1 -6
  106. package/src/value/json.ts +4 -4
  107. package/src/workbench/DatabaseWorkbench.tsx +14 -6
  108. package/src/workbench/DesignerTab.tsx +1 -1
  109. package/src/workbench/tabs.ts +5 -8
@@ -15,13 +15,8 @@ export const parseRowKey = (key: string): RowRef =>
15
15
 
16
16
  export const buildGridColumns = (loaded: Pick<RowsResult, 'columns'>, structure: TableStructure | null): GridColumn[] => {
17
17
  const keys = { primaryKey: structure?.primaryKey ?? [], foreignKey: structure?.foreignKeys.flatMap((key) => key.columns) ?? [] };
18
- return loaded.columns.map((column) =>
19
- gridColumnOf(
20
- column,
21
- structure?.columns.find((info) => info.name === column.name),
22
- keys
23
- )
24
- );
18
+ const infoOf = (name: string) => structure?.columns.find((info) => info.name === name);
19
+ return loaded.columns.map((column) => gridColumnOf(column, infoOf(column.name), keys));
25
20
  };
26
21
 
27
22
  export interface GridRowsInput {
@@ -2,7 +2,10 @@ import type { GridLayout } from '../grid/column-layout.ts';
2
2
  import type { StoredFilter } from './command-field.ts';
3
3
  import { PAGE_SIZES } from './paging.ts';
4
4
 
5
- /* Bump when the shape changes; a stored value of another version is ignored rather than migrated. `filters` was added alongside `where`, which still says the same thing for a value without it. */
5
+ /*
6
+ * Bump when the shape changes; a stored value of another version is ignored rather than migrated. `filters`
7
+ * came later without a bump, since `where` still says the same thing for a value without it.
8
+ */
6
9
  export const LAYOUT_VERSION = 1;
7
10
 
8
11
  /* What is remembered of one table's view between visits. */
@@ -20,11 +23,11 @@ export const layoutStorageKey = (connectionId: string, schema: string, table: st
20
23
 
21
24
  const isStringList = (value: unknown): value is string[] => Array.isArray(value) && value.every((item) => typeof item === 'string');
22
25
 
26
+ const isRecord = (value: unknown): value is Record<string, unknown> => value !== null && typeof value === 'object' && !Array.isArray(value);
27
+
23
28
  const isStoredFilter = (value: unknown): value is StoredFilter =>
24
29
  isRecord(value) && typeof value.text === 'string' && typeof value.sql === 'string' && value.sql.trim() !== '';
25
30
 
26
- const isRecord = (value: unknown): value is Record<string, unknown> => value !== null && typeof value === 'object' && !Array.isArray(value);
27
-
28
31
  export const serializeLayout = (layout: StoredLayout): string => JSON.stringify({ version: LAYOUT_VERSION, ...layout });
29
32
 
30
33
  /* The layout a stored text holds, or `null` when it is missing, broken or from another version. A field that is off is dropped alone. */
@@ -112,8 +112,6 @@ export const rowKeyOf = (structure: TableStructure, loaded: LoadedRows, row: num
112
112
  return key;
113
113
  };
114
114
 
115
- const numeric = (text: string): number => Number(text);
116
-
117
115
  /* Updates, then deletes, then inserts, all computed against the page as loaded. Throws for a change on a row without a usable key, which the grid never lets a person make. */
118
116
  export const toRowChanges = (structure: TableStructure, loaded: LoadedRows, pending: PendingChanges): RowChange[] => {
119
117
  const keyOf = (row: number): RowKey => {
@@ -124,7 +122,7 @@ export const toRowChanges = (structure: TableStructure, loaded: LoadedRows, pend
124
122
  return key;
125
123
  };
126
124
  const updates = Object.keys(pending.edits)
127
- .map(numeric)
125
+ .map(Number)
128
126
  .filter((row) => !pending.deletes.has(row))
129
127
  .sort((left, right) => left - right)
130
128
  .map((row): RowChange => ({ kind: 'update', key: keyOf(row), values: pending.edits[row]! }));
@@ -133,11 +131,18 @@ export const toRowChanges = (structure: TableStructure, loaded: LoadedRows, pend
133
131
  return [...updates, ...deletes, ...inserts];
134
132
  };
135
133
 
134
+ const keyValueText = (value: Value): string => {
135
+ if (value === null) {
136
+ return 'NULL';
137
+ }
138
+ if (typeof value === 'object') {
139
+ return `0x${value.hex}`;
140
+ }
141
+ return typeof value === 'string' ? `'${value}'` : String(value);
142
+ };
143
+
136
144
  /* A key as a person would write it in a WHERE: `id = 5, tenant = 'a'`. */
137
145
  export const describeKey = (key: RowKey): string =>
138
146
  Object.entries(key)
139
- .map(
140
- ([column, value]) =>
141
- `${column} = ${value === null ? 'NULL' : typeof value === 'object' ? `0x${value.hex}` : typeof value === 'string' ? `'${value}'` : String(value)}`
142
- )
147
+ .map(([column, value]) => `${column} = ${keyValueText(value)}`)
143
148
  .join(', ');
@@ -1,7 +1,7 @@
1
1
  import { useEffect, useRef, useState } from 'react';
2
2
  import { messageOf } from '@adecore/ui';
3
3
  import type { DatabaseFiles } from '../actions.ts';
4
- import { DatabaseRequestError, type DatabaseClient, type DatabaseSession } from '../client/types.ts';
4
+ import { isCancelled, type DatabaseClient, type DatabaseSession } from '../client/types.ts';
5
5
  import type { FileFormat, TableStructure } from '../protocol/index.ts';
6
6
  import type { ImportSample } from './ImportDialog.tsx';
7
7
  import { formatOfPath, importableColumns, mapColumn, matchColumns, type ImportFormat } from './import-mapping.ts';
@@ -13,6 +13,9 @@ export type TransferNotice =
13
13
  | { readonly kind: 'imported'; readonly rows: number }
14
14
  | { readonly kind: 'failed'; readonly during: 'export' | 'import'; readonly message: string };
15
15
 
16
+ /* How long a finished export or import stays on the strip. */
17
+ const NOTICE_MS = 8000;
18
+
16
19
  /* The import a person is setting up. */
17
20
  export interface ImportDraft {
18
21
  readonly path: string;
@@ -68,7 +71,7 @@ export function useTableTransfer({ client, session, files, schema, table, struct
68
71
  if (notice?.kind !== 'exported' && notice?.kind !== 'imported') {
69
72
  return;
70
73
  }
71
- const timer = setTimeout(() => setNotice(null), 8000);
74
+ const timer = setTimeout(() => setNotice(null), NOTICE_MS);
72
75
  return () => clearTimeout(timer);
73
76
  }, [notice]);
74
77
 
@@ -102,8 +105,7 @@ export function useTableTransfer({ client, session, files, schema, table, struct
102
105
  );
103
106
  show({ kind: 'exported', rows: result.rows });
104
107
  } catch (error) {
105
- const cancelled = controller.signal.aborted || (error instanceof DatabaseRequestError && error.code === 'cancelled');
106
- if (cancelled) {
108
+ if (controller.signal.aborted || isCancelled(error)) {
107
109
  setNotice(null);
108
110
  } else {
109
111
  show({ kind: 'failed', during: 'export', message: messageOf(error) });
@@ -153,7 +155,7 @@ export function useTableTransfer({ client, session, files, schema, table, struct
153
155
  now === null || now.path !== path ? now : { ...now, header, sample: read, mapping: matchColumns(read.columns, tableColumns, header) }
154
156
  );
155
157
  } catch (error) {
156
- if (!(error instanceof DatabaseRequestError && error.code === 'cancelled')) {
158
+ if (!isCancelled(error)) {
157
159
  setImportDraft((now) => (now === null || now.path !== path ? now : { ...now, error: messageOf(error) }));
158
160
  }
159
161
  }
@@ -119,14 +119,18 @@ const toTables = (database: FakeDatabase): Map<string, Map<string, Table>> =>
119
119
  ])
120
120
  );
121
121
 
122
- const findTable = (session: Session, schema: string, name: string): Table => {
122
+ const findSchema = (session: Session, schema: string): Map<string, Table> => {
123
123
  const tables = session.schemas.get(schema);
124
124
 
125
125
  if (tables === undefined) {
126
126
  throw new FakeError('query-failed', `Unknown database '${schema}'`, '42000');
127
127
  }
128
128
 
129
- const table = tables.get(name);
129
+ return tables;
130
+ };
131
+
132
+ const findTable = (session: Session, schema: string, name: string): Table => {
133
+ const table = findSchema(session, schema).get(name);
130
134
 
131
135
  if (table === undefined) {
132
136
  throw new FakeError('query-failed', `Table '${schema}.${name}' doesn't exist`, '42S02');
@@ -334,10 +338,7 @@ const selectSchema = (session: Session, schema: string | undefined): void => {
334
338
  return;
335
339
  }
336
340
 
337
- if (!session.schemas.has(schema)) {
338
- throw new FakeError('query-failed', `Unknown database '${schema}'`, '42000');
339
- }
340
-
341
+ findSchema(session, schema);
341
342
  session.schema = schema;
342
343
  };
343
344
 
@@ -402,12 +403,7 @@ export const fakeDatabaseTransport = (options: FakeDatabaseTransportOptions): Da
402
403
  schemas: [...sessionOf(session).schemas.keys()].map((name) => ({ name, system: SYSTEM_SCHEMAS.includes(name) }))
403
404
  }),
404
405
  tables: ({ session, schema }) => {
405
- const tables = sessionOf(session).schemas.get(schema);
406
-
407
- if (tables === undefined) {
408
- throw new FakeError('query-failed', `Unknown database '${schema}'`, '42000');
409
- }
410
-
406
+ const tables = findSchema(sessionOf(session), schema);
411
407
  return { tables: [...tables].map(([name, table]) => ({ name, kind: table.kind, rowEstimate: table.rows.length, comment: null })) };
412
408
  },
413
409
  structure: ({ session, schema, table: name }) => {
@@ -96,7 +96,7 @@ export function ValueEditor({ column, value, editable, onCommit }: ValueEditorPr
96
96
  } else if (rendered !== null && rendered.kind === 'text') {
97
97
  copyText(rendered.text);
98
98
  } else {
99
- copyText(draft.mode === 'value' ? draft.text : draft.mode === 'null' ? 'NULL' : 'DEFAULT');
99
+ copyText(placeholder ?? draft.text);
100
100
  }
101
101
  };
102
102
 
@@ -25,12 +25,7 @@ export type DraftProblem = { readonly kind: 'json'; readonly reason: string } |
25
25
  export type Conversion = { readonly ok: true; readonly value: EditValue } | { readonly ok: false; readonly problem: DraftProblem };
26
26
 
27
27
  /* The text a value is edited as: a binary value as its hex, everything else as it is written. */
28
- const textOf = (value: Exclude<Value, null>): string => {
29
- if (typeof value === 'object') {
30
- return value.hex;
31
- }
32
- return String(value);
33
- };
28
+ const textOf = (value: Exclude<Value, null>): string => (typeof value === 'object' ? value.hex : String(value));
34
29
 
35
30
  export const sourceOf = (column: Pick<GridColumn, 'name' | 'type' | 'kind'>, value: Value): Source => {
36
31
  const binary = column.kind === 'binary' || (value !== null && typeof value === 'object');
package/src/value/json.ts CHANGED
@@ -49,11 +49,11 @@ export const prettyJson = (text: string): string | null => {
49
49
  continue;
50
50
  }
51
51
  if (char === '{' || char === '[') {
52
- const empty = nextToken(i + 1) === (char === '{' ? '}' : ']');
52
+ const close = char === '{' ? '}' : ']';
53
53
  output += char;
54
- if (empty) {
55
- output += char === '{' ? '}' : ']';
56
- i = text.indexOf(char === '{' ? '}' : ']', i + 1) + 1;
54
+ if (nextToken(i + 1) === close) {
55
+ output += close;
56
+ i = text.indexOf(close, i + 1) + 1;
57
57
  continue;
58
58
  }
59
59
  depth++;
@@ -1,6 +1,6 @@
1
1
  import { useEffect, useMemo, useRef, useState, type KeyboardEvent, type Ref } from 'react';
2
2
  import clsx from 'clsx';
3
- import { PencilRuler, Settings2, SquareTerminal, Table, TableProperties } from 'lucide-react';
3
+ import { PencilRuler, Settings2, SquareTerminal, Table, TableProperties, type LucideIcon } from 'lucide-react';
4
4
  import { useTranslation } from 'react-i18next';
5
5
  import {
6
6
  Button,
@@ -26,6 +26,7 @@ import type { Connection } from '../client/types.ts';
26
26
  import { ConnectionManager } from '../connections/ConnectionManager.tsx';
27
27
  import { QueryConsole } from '../console/QueryConsole.tsx';
28
28
  import { DatabaseExplorer } from '../explorer/DatabaseExplorer.tsx';
29
+ import { useStableCallback } from '../use-stable-callback.ts';
29
30
  import { DesignerTab } from './DesignerTab.tsx';
30
31
  import { TableTab } from './TableTab.tsx';
31
32
  import {
@@ -44,7 +45,6 @@ import {
44
45
  STORAGE_KEY,
45
46
  type WorkbenchTab
46
47
  } from './tabs.ts';
47
- import { useStableCallback } from '../use-stable-callback.ts';
48
48
 
49
49
  export interface DatabaseWorkbenchProps {
50
50
  connections: readonly Connection[];
@@ -56,6 +56,13 @@ export interface DatabaseWorkbenchProps {
56
56
 
57
57
  const DEFAULT_WIDTH = 280;
58
58
  const MIN_WIDTH = 200;
59
+ const CLOSE_TAB = shortcut('Mod+W');
60
+
61
+ const TAB_ICON: Record<WorkbenchTab['kind'], LucideIcon> = {
62
+ table: Table,
63
+ console: SquareTerminal,
64
+ designer: PencilRuler
65
+ };
59
66
 
60
67
  /*
61
68
  * The whole database view in one piece: the explorer beside closable tabs for the tables, consoles and
@@ -83,8 +90,9 @@ export function DatabaseWorkbench({ connections, onConnectionsChange, className,
83
90
  if (pruned !== state) {
84
91
  setState(pruned);
85
92
  }
86
- if ([...dirty].some((id) => !state.tabs.some((tab) => tab.id === id))) {
87
- setDirty(new Set([...dirty].filter((id) => state.tabs.some((tab) => tab.id === id))));
93
+ const isOpen = (id: string): boolean => state.tabs.some((tab) => tab.id === id);
94
+ if (![...dirty].every(isOpen)) {
95
+ setDirty(new Set([...dirty].filter(isOpen)));
88
96
  }
89
97
  const tabs = state.tabs.filter((tab) => known.has(connectionIdOf(tab)));
90
98
  const active = tabs.find((tab) => tab.id === state.activeId) ?? tabs.at(-1) ?? null;
@@ -146,7 +154,7 @@ export function DatabaseWorkbench({ connections, onConnectionsChange, className,
146
154
  active !== null &&
147
155
  e.currentTarget.contains(e.target as Node) &&
148
156
  !e.nativeEvent.isComposing &&
149
- matchesShortcut(shortcut('Mod+W'), e.nativeEvent, isApplePlatform())
157
+ matchesShortcut(CLOSE_TAB, e.nativeEvent, isApplePlatform())
150
158
  ) {
151
159
  e.preventDefault();
152
160
  requestClose(active.id);
@@ -277,7 +285,7 @@ export function DatabaseWorkbench({ connections, onConnectionsChange, className,
277
285
  >
278
286
  {tabs.map((tab) => (
279
287
  <Tabs.Tab key={tab.id} value={tab.id} onClose={() => requestClose(tab.id)}>
280
- <Icon icon={tab.kind === 'table' ? Table : tab.kind === 'console' ? SquareTerminal : PencilRuler} size={14} />
288
+ <Icon icon={TAB_ICON[tab.kind]} size={14} />
281
289
  {titleOf(tab)}
282
290
  </Tabs.Tab>
283
291
  ))}
@@ -3,8 +3,8 @@ import type { DatabaseAction } from '../actions.ts';
3
3
  import { useDatabaseAction, useDatabaseClient, useDatabaseFiles, useDatabaseStorage, useNumberNotation } from '../client-context.ts';
4
4
  import type { Connection } from '../client/types.ts';
5
5
  import { TableDesigner } from '../designer/TableDesigner.tsx';
6
- import type { WorkbenchTab } from './tabs.ts';
7
6
  import { useStableCallback } from '../use-stable-callback.ts';
7
+ import type { WorkbenchTab } from './tabs.ts';
8
8
 
9
9
  export interface DesignerTabProps {
10
10
  connection: Connection;
@@ -25,6 +25,9 @@ export const connectionIdOf = (tab: WorkbenchTab): string => (tab.kind === 'tabl
25
25
 
26
26
  const tableTabId = (ref: TableRef): string => `table:${ref.connectionId}\u0000${ref.schema}\u0000${ref.table}`;
27
27
 
28
+ const isDesignerOf = (tab: WorkbenchTab, connectionId: string, schema: string, table: string): boolean =>
29
+ tab.kind === 'designer' && tab.connectionId === connectionId && tab.schema === schema && tab.table === table;
30
+
28
31
  const add = (state: WorkbenchState, tab: WorkbenchTab): WorkbenchState => ({ ...state, tabs: [...state.tabs, tab], activeId: tab.id });
29
32
 
30
33
  const update = (state: WorkbenchState, id: string, change: (tab: WorkbenchTab) => WorkbenchTab): WorkbenchState => {
@@ -64,10 +67,7 @@ export const openConsole = (state: WorkbenchState, connectionId: string, schema?
64
67
 
65
68
  /* A table is designed in one tab; a new table gets a tab each time. */
66
69
  export const openDesigner = (state: WorkbenchState, connectionId: string, schema: string, table?: string): WorkbenchState => {
67
- const existing =
68
- table === undefined
69
- ? undefined
70
- : state.tabs.find((tab) => tab.kind === 'designer' && tab.connectionId === connectionId && tab.schema === schema && tab.table === table);
70
+ const existing = table === undefined ? undefined : state.tabs.find((tab) => isDesignerOf(tab, connectionId, schema, table));
71
71
  if (existing !== undefined) {
72
72
  return focusTab(state, existing.id);
73
73
  }
@@ -87,10 +87,7 @@ export const setDesignerTable = (state: WorkbenchState, id: string, table: strin
87
87
  if (subject?.kind !== 'designer' || subject.table === table) {
88
88
  return state;
89
89
  }
90
- const tabs = state.tabs.filter(
91
- (tab) =>
92
- tab.id === id || !(tab.kind === 'designer' && tab.connectionId === subject.connectionId && tab.schema === subject.schema && tab.table === table)
93
- );
90
+ const tabs = state.tabs.filter((tab) => tab.id === id || !isDesignerOf(tab, subject.connectionId, subject.schema, table));
94
91
  return update({ ...state, tabs }, id, (tab) => (tab.kind === 'designer' ? { ...tab, table } : tab));
95
92
  };
96
93