tablewalk 0.0.1
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 +553 -0
- package/dist/adapters/adapter.js +372 -0
- package/dist/adapters/connect.js +33 -0
- package/dist/adapters/mysql.js +951 -0
- package/dist/adapters/postgres.js +1000 -0
- package/dist/adapters/sqlite.js +781 -0
- package/dist/client/agent.js +262 -0
- package/dist/client/app.js +973 -0
- package/dist/client/arrange.js +254 -0
- package/dist/client/ask.js +133 -0
- package/dist/client/breakdown.js +317 -0
- package/dist/client/clauses.js +390 -0
- package/dist/client/columns.js +98 -0
- package/dist/client/complete.js +437 -0
- package/dist/client/compose.js +166 -0
- package/dist/client/composer.css +495 -0
- package/dist/client/composer.js +1972 -0
- package/dist/client/connections.js +234 -0
- package/dist/client/connmanager.js +962 -0
- package/dist/client/connurl.js +188 -0
- package/dist/client/core.js +893 -0
- package/dist/client/deeplink.js +270 -0
- package/dist/client/delete.js +144 -0
- package/dist/client/diagram.js +885 -0
- package/dist/client/dropdown.js +279 -0
- package/dist/client/export.js +456 -0
- package/dist/client/features.css +524 -0
- package/dist/client/findvalue.js +169 -0
- package/dist/client/grid.js +205 -0
- package/dist/client/handoff.js +153 -0
- package/dist/client/help.css +145 -0
- package/dist/client/help.js +881 -0
- package/dist/client/history.js +222 -0
- package/dist/client/index.html +116 -0
- package/dist/client/insert.js +151 -0
- package/dist/client/menu.js +160 -0
- package/dist/client/nested.js +255 -0
- package/dist/client/page.css +713 -0
- package/dist/client/page.js +1345 -0
- package/dist/client/pagebuilder.js +1222 -0
- package/dist/client/pagemarks.js +95 -0
- package/dist/client/palette.js +374 -0
- package/dist/client/peek.js +254 -0
- package/dist/client/picker.js +139 -0
- package/dist/client/pins.js +140 -0
- package/dist/client/prompt.js +129 -0
- package/dist/client/record.js +707 -0
- package/dist/client/schemaexport.js +242 -0
- package/dist/client/schematext.js +125 -0
- package/dist/client/shape.js +178 -0
- package/dist/client/shapecheck.js +129 -0
- package/dist/client/skeleton.js +139 -0
- package/dist/client/sql.css +126 -0
- package/dist/client/sql.js +398 -0
- package/dist/client/sqlcomplete.js +163 -0
- package/dist/client/sqlsaved.js +107 -0
- package/dist/client/style.css +2711 -0
- package/dist/client/summary.js +259 -0
- package/dist/client/table.js +1035 -0
- package/dist/client/template.js +539 -0
- package/dist/client/theme.js +74 -0
- package/dist/client/tour.js +324 -0
- package/dist/client/undo.js +105 -0
- package/dist/client/url.js +166 -0
- package/dist/client/value.js +223 -0
- package/dist/client/views.js +215 -0
- package/dist/client/virtual.js +176 -0
- package/dist/client/welcome.js +170 -0
- package/dist/client/write.js +414 -0
- package/dist/server/changeimpact.js +195 -0
- package/dist/server/connections.js +615 -0
- package/dist/server/constraints.js +62 -0
- package/dist/server/credentials.js +230 -0
- package/dist/server/fixture.js +199 -0
- package/dist/server/graph.js +194 -0
- package/dist/server/impact.js +48 -0
- package/dist/server/index.js +2204 -0
- package/dist/server/journal.js +173 -0
- package/dist/server/layouts.js +128 -0
- package/dist/server/mcp.js +2840 -0
- package/dist/server/shapeonly.js +91 -0
- package/dist/shared/breakdown.js +231 -0
- package/dist/shared/breakdowntext.js +257 -0
- package/dist/shared/diff.js +130 -0
- package/dist/shared/like.js +29 -0
- package/dist/shared/lint.js +149 -0
- package/dist/shared/order.js +133 -0
- package/dist/shared/page.js +932 -0
- package/dist/shared/query.js +831 -0
- package/dist/shared/recordview.js +343 -0
- package/dist/shared/schema.js +377 -0
- package/dist/shared/sqlsaved.js +67 -0
- package/dist/shared/view.js +981 -0
- package/dist/shared/viewtext.js +273 -0
- package/dist/shared/vocabulary.js +164 -0
- package/package.json +57 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The connection picker.
|
|
3
|
+
*
|
|
4
|
+
* The server holds the connection strings and this file never sees one — the
|
|
5
|
+
* list it renders carries an id, a name, a dialect and a redacted detail. A
|
|
6
|
+
* password cannot leak through a UI that was never given one.
|
|
7
|
+
*
|
|
8
|
+
* Connections added here are session-only. Writing them back to the config
|
|
9
|
+
* file would mean a web page quietly editing a file on disk, which is a
|
|
10
|
+
* surprise nobody asked for; the dialog says so rather than leaving you to
|
|
11
|
+
* discover it after a restart.
|
|
12
|
+
*/
|
|
13
|
+
import { $, api, connectionChanged, el, render, state, svgEl, toast } from './core.js';
|
|
14
|
+
import { dropdown } from './dropdown.js';
|
|
15
|
+
import { syncWriteMode } from './write.js';
|
|
16
|
+
import { openConnections } from './connmanager.js';
|
|
17
|
+
import { clearPeekCache } from './peek.js';
|
|
18
|
+
import { forgetPages } from './page.js';
|
|
19
|
+
import { loadSavedSql } from './sqlsaved.js';
|
|
20
|
+
|
|
21
|
+
export async function initConnections(onSwitched) {
|
|
22
|
+
state.onSwitched = onSwitched;
|
|
23
|
+
await refreshList();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Read the schema again, for the connection in hand.
|
|
28
|
+
*
|
|
29
|
+
* What changed is reported rather than swallowed: "nothing changed" is a real
|
|
30
|
+
* answer to "did my migration land", and a button that always says "done"
|
|
31
|
+
* cannot tell you it did nothing.
|
|
32
|
+
*/
|
|
33
|
+
async function reintrospect() {
|
|
34
|
+
const before = state.schema;
|
|
35
|
+
try {
|
|
36
|
+
const { schema } = await api('/api/connections/refresh', { id: state.activeConnection });
|
|
37
|
+
state.schema = schema;
|
|
38
|
+
/* Rows cached against the old schema describe columns that may no longer
|
|
39
|
+
exist. The connection is unchanged, so the peek cache's own key does
|
|
40
|
+
not rule them out — this is the one case where it has to be emptied. */
|
|
41
|
+
clearPeekCache();
|
|
42
|
+
forgetPages();
|
|
43
|
+
|
|
44
|
+
const was = new Set((before?.tables ?? []).map((t) => t.id));
|
|
45
|
+
const now = new Set(schema.tables.map((t) => t.id));
|
|
46
|
+
const added = [...now].filter((id) => !was.has(id));
|
|
47
|
+
const gone = [...was].filter((id) => !now.has(id));
|
|
48
|
+
const columnsBefore = (before?.tables ?? []).reduce((n, t) => n + t.columns.length, 0);
|
|
49
|
+
const columnsNow = schema.tables.reduce((n, t) => n + t.columns.length, 0);
|
|
50
|
+
|
|
51
|
+
const changes = [
|
|
52
|
+
added.length ? `${added.length} table${added.length === 1 ? '' : 's'} added` : null,
|
|
53
|
+
gone.length ? `${gone.length} removed` : null,
|
|
54
|
+
columnsNow !== columnsBefore ? `${Math.abs(columnsNow - columnsBefore)} column${Math.abs(columnsNow - columnsBefore) === 1 ? '' : 's'} ${columnsNow > columnsBefore ? 'added' : 'removed'}` : null,
|
|
55
|
+
].filter(Boolean);
|
|
56
|
+
|
|
57
|
+
state.onSwitched?.();
|
|
58
|
+
render();
|
|
59
|
+
toast(
|
|
60
|
+
changes.length
|
|
61
|
+
? `Schema re-read — ${changes.join(', ')}.`
|
|
62
|
+
: `Schema re-read — nothing changed (${schema.tables.length} tables).`,
|
|
63
|
+
'ok',
|
|
64
|
+
);
|
|
65
|
+
} catch (err) {
|
|
66
|
+
toast(err.message, 'error');
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Redraw the picker from the server's list.
|
|
72
|
+
*
|
|
73
|
+
* Exported because a deep link switches connection without going through
|
|
74
|
+
* `selectConnection` — it opens the connection the link names and rebuilds
|
|
75
|
+
* the trail — and the picker was left showing whatever the server called
|
|
76
|
+
* active. Landing on a link into one database with the sidebar naming another
|
|
77
|
+
* is the interface disagreeing with itself about where you are.
|
|
78
|
+
*/
|
|
79
|
+
export async function refreshConnections() {
|
|
80
|
+
await refreshList();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function refreshList() {
|
|
84
|
+
let data;
|
|
85
|
+
try {
|
|
86
|
+
data = await api('/api/connections');
|
|
87
|
+
} catch {
|
|
88
|
+
/* One connection, no config file: the picker has nothing to offer, so it
|
|
89
|
+
gets out of the way rather than showing a control with one option.
|
|
90
|
+
|
|
91
|
+
`conn-select` was the `<select>` this used to be, and there has been no
|
|
92
|
+
such element since it became a custom dropdown — so this line, on the
|
|
93
|
+
one path that runs when the connection list cannot be read, threw a
|
|
94
|
+
TypeError out of the handler meant to recover from the failure. It
|
|
95
|
+
escaped `initConnections`, which boot awaits, and the page never
|
|
96
|
+
finished starting: a server hiccup on one request took the whole app
|
|
97
|
+
down rather than hiding a picker. */
|
|
98
|
+
$('conn-picker').hidden = true;
|
|
99
|
+
$('db-label').hidden = false;
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
state.connections = data.connections;
|
|
103
|
+
state.activeConnection = data.active;
|
|
104
|
+
|
|
105
|
+
/* Write mode is remembered per connection, and until now it was only
|
|
106
|
+
re-read when someone *switched* connection — never on the way in. So a
|
|
107
|
+
page reload landed on the active connection with write mode off, and the
|
|
108
|
+
setting quietly came back the moment you switched away and back. `initWriteMode`
|
|
109
|
+
runs before this, when `state.activeConnection` is still undefined, so
|
|
110
|
+
there is nothing it could have read. */
|
|
111
|
+
syncWriteMode();
|
|
112
|
+
|
|
113
|
+
const picker = $('conn-picker');
|
|
114
|
+
// A lone connection with no config file is not worth a picker.
|
|
115
|
+
picker.hidden = data.connections.length < 2 && !data.connections.some((c) => c.source === 'config');
|
|
116
|
+
/* The picker and the plain `name · dialect` label answer the same question,
|
|
117
|
+
so exactly one of them is on screen. With a single unconfigured database
|
|
118
|
+
there is nothing to pick between and the label is the honest form — and
|
|
119
|
+
it still opens Connections, which is how a second one gets added. */
|
|
120
|
+
$('db-label').hidden = !picker.hidden;
|
|
121
|
+
if (picker.hidden) return;
|
|
122
|
+
|
|
123
|
+
const control = dropdown({
|
|
124
|
+
ariaLabel: 'Database connection',
|
|
125
|
+
value: data.active,
|
|
126
|
+
/* The detail is the redacted form — host and database, never a password.
|
|
127
|
+
It is the second line rather than a tooltip because it is what tells
|
|
128
|
+
two connections called "app" apart, and a tooltip you have to hover to
|
|
129
|
+
read is no help when choosing between them. */
|
|
130
|
+
items: data.connections.map((conn) => ({
|
|
131
|
+
value: conn.id,
|
|
132
|
+
label: conn.name,
|
|
133
|
+
detail: conn.detail,
|
|
134
|
+
badge: conn.dialect,
|
|
135
|
+
tone: conn.error ? 'error' : conn.connected ? 'ok' : undefined,
|
|
136
|
+
})),
|
|
137
|
+
onChange: (id) => void selectConnection(id),
|
|
138
|
+
actions: [
|
|
139
|
+
/* Re-introspection, on demand.
|
|
140
|
+
|
|
141
|
+
The schema is read once when a connection is opened and then trusted,
|
|
142
|
+
which is right for browsing and wrong the moment the database changes
|
|
143
|
+
underneath you — a migration runs in another window and tablewalk goes
|
|
144
|
+
on describing the schema it met at boot, reporting a real column as
|
|
145
|
+
unknown. Rather than polling for a change that almost never happens,
|
|
146
|
+
this is the button for the moment you know it did. */
|
|
147
|
+
{ label: '↻ Re-read the schema', onSelect: () => void reintrospect() },
|
|
148
|
+
],
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
picker.replaceChildren(
|
|
152
|
+
control,
|
|
153
|
+
/* Out of the dropdown and onto the row.
|
|
154
|
+
|
|
155
|
+
Managing connections was the last item of a menu you open to switch
|
|
156
|
+
databases — findable only by someone who had already opened it looking
|
|
157
|
+
for something else. It is a destination, not a variation on the thing
|
|
158
|
+
above it. */
|
|
159
|
+
el('button', {
|
|
160
|
+
type: 'button',
|
|
161
|
+
class: 'conn-manage',
|
|
162
|
+
title: 'Manage connections',
|
|
163
|
+
'aria-label': 'Manage connections',
|
|
164
|
+
onclick: () => void openConnections(),
|
|
165
|
+
}, svgEl('svg', {
|
|
166
|
+
/* A cog with teeth, not a circle with spokes. The first draft was a
|
|
167
|
+
circle and eight radiating lines, which is a *sun* — it read as a
|
|
168
|
+
brightness or theme control sitting next to the database picker, and
|
|
169
|
+
the tooltip was doing all the work of saying otherwise. An icon that
|
|
170
|
+
has to be hovered to be understood is not doing its job. */
|
|
171
|
+
viewBox: '0 0 24 24', width: '17', height: '17', 'aria-hidden': 'true',
|
|
172
|
+
fill: 'none', stroke: 'currentColor', 'stroke-width': '2',
|
|
173
|
+
'stroke-linecap': 'round', 'stroke-linejoin': 'round',
|
|
174
|
+
}, [
|
|
175
|
+
svgEl('path', {
|
|
176
|
+
d: 'M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08'
|
|
177
|
+
+ 'a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51'
|
|
178
|
+
+ 'a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08'
|
|
179
|
+
+ 'a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18'
|
|
180
|
+
+ 'a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39'
|
|
181
|
+
+ 'a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09'
|
|
182
|
+
+ 'a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25'
|
|
183
|
+
+ 'a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z',
|
|
184
|
+
}),
|
|
185
|
+
svgEl('circle', { cx: '12', cy: '12', r: '3' }),
|
|
186
|
+
])),
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Switch database, and reset everything that belonged to the last one.
|
|
192
|
+
*
|
|
193
|
+
* Exported because the command palette can switch connections too, and two
|
|
194
|
+
* implementations of "open a different database" would drift on exactly the
|
|
195
|
+
* parts that matter — clearing the trail, the write mode, and the query bar.
|
|
196
|
+
*/
|
|
197
|
+
export async function selectConnection(id) {
|
|
198
|
+
if (id === state.activeConnection) return;
|
|
199
|
+
const previous = state.activeConnection;
|
|
200
|
+
try {
|
|
201
|
+
const { schema } = await api('/api/connections/select', { id });
|
|
202
|
+
state.schema = schema;
|
|
203
|
+
state.activeConnection = id;
|
|
204
|
+
/* Switching database invalidates the trail entirely: a crumb naming a
|
|
205
|
+
table in the old one would look like a step you could walk back to. */
|
|
206
|
+
state.stack = [];
|
|
207
|
+
state.page = 0;
|
|
208
|
+
$('query').value = '';
|
|
209
|
+
$('db-label').textContent = `${schema.label} · ${schema.dialect}`;
|
|
210
|
+
document.title = `${schema.label} — tablewalk`;
|
|
211
|
+
await refreshList();
|
|
212
|
+
// The new connection has its own write setting, and must not inherit the
|
|
213
|
+
// previous one in either direction.
|
|
214
|
+
syncWriteMode();
|
|
215
|
+
/* The new connection's committed statements, which the old ones are not.
|
|
216
|
+
Without this the saved bar kept whatever the last connection had until
|
|
217
|
+
the page was reloaded. */
|
|
218
|
+
await loadSavedSql();
|
|
219
|
+
state.onSwitched?.();
|
|
220
|
+
/* Everything that lives outside the rendered view and is keyed to a
|
|
221
|
+
database — the SQL editor is the one today — rebuilds here, after the
|
|
222
|
+
schema and the per-connection loads have landed. */
|
|
223
|
+
connectionChanged();
|
|
224
|
+
render();
|
|
225
|
+
} catch (err) {
|
|
226
|
+
toast(err.message, 'error');
|
|
227
|
+
/* Redrawn from the server rather than set back by hand. The same dead
|
|
228
|
+
`conn-select` was assigned here, so a failed switch threw a second
|
|
229
|
+
error on top of the one it was reporting — and even spelled correctly,
|
|
230
|
+
a custom dropdown has no `.value` to put back. Asking the server which
|
|
231
|
+
connection is active is the only answer that cannot drift from it. */
|
|
232
|
+
await refreshList();
|
|
233
|
+
}
|
|
234
|
+
}
|