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.
Files changed (97) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +553 -0
  3. package/dist/adapters/adapter.js +372 -0
  4. package/dist/adapters/connect.js +33 -0
  5. package/dist/adapters/mysql.js +951 -0
  6. package/dist/adapters/postgres.js +1000 -0
  7. package/dist/adapters/sqlite.js +781 -0
  8. package/dist/client/agent.js +262 -0
  9. package/dist/client/app.js +973 -0
  10. package/dist/client/arrange.js +254 -0
  11. package/dist/client/ask.js +133 -0
  12. package/dist/client/breakdown.js +317 -0
  13. package/dist/client/clauses.js +390 -0
  14. package/dist/client/columns.js +98 -0
  15. package/dist/client/complete.js +437 -0
  16. package/dist/client/compose.js +166 -0
  17. package/dist/client/composer.css +495 -0
  18. package/dist/client/composer.js +1972 -0
  19. package/dist/client/connections.js +234 -0
  20. package/dist/client/connmanager.js +962 -0
  21. package/dist/client/connurl.js +188 -0
  22. package/dist/client/core.js +893 -0
  23. package/dist/client/deeplink.js +270 -0
  24. package/dist/client/delete.js +144 -0
  25. package/dist/client/diagram.js +885 -0
  26. package/dist/client/dropdown.js +279 -0
  27. package/dist/client/export.js +456 -0
  28. package/dist/client/features.css +524 -0
  29. package/dist/client/findvalue.js +169 -0
  30. package/dist/client/grid.js +205 -0
  31. package/dist/client/handoff.js +153 -0
  32. package/dist/client/help.css +145 -0
  33. package/dist/client/help.js +881 -0
  34. package/dist/client/history.js +222 -0
  35. package/dist/client/index.html +116 -0
  36. package/dist/client/insert.js +151 -0
  37. package/dist/client/menu.js +160 -0
  38. package/dist/client/nested.js +255 -0
  39. package/dist/client/page.css +713 -0
  40. package/dist/client/page.js +1345 -0
  41. package/dist/client/pagebuilder.js +1222 -0
  42. package/dist/client/pagemarks.js +95 -0
  43. package/dist/client/palette.js +374 -0
  44. package/dist/client/peek.js +254 -0
  45. package/dist/client/picker.js +139 -0
  46. package/dist/client/pins.js +140 -0
  47. package/dist/client/prompt.js +129 -0
  48. package/dist/client/record.js +707 -0
  49. package/dist/client/schemaexport.js +242 -0
  50. package/dist/client/schematext.js +125 -0
  51. package/dist/client/shape.js +178 -0
  52. package/dist/client/shapecheck.js +129 -0
  53. package/dist/client/skeleton.js +139 -0
  54. package/dist/client/sql.css +126 -0
  55. package/dist/client/sql.js +398 -0
  56. package/dist/client/sqlcomplete.js +163 -0
  57. package/dist/client/sqlsaved.js +107 -0
  58. package/dist/client/style.css +2711 -0
  59. package/dist/client/summary.js +259 -0
  60. package/dist/client/table.js +1035 -0
  61. package/dist/client/template.js +539 -0
  62. package/dist/client/theme.js +74 -0
  63. package/dist/client/tour.js +324 -0
  64. package/dist/client/undo.js +105 -0
  65. package/dist/client/url.js +166 -0
  66. package/dist/client/value.js +223 -0
  67. package/dist/client/views.js +215 -0
  68. package/dist/client/virtual.js +176 -0
  69. package/dist/client/welcome.js +170 -0
  70. package/dist/client/write.js +414 -0
  71. package/dist/server/changeimpact.js +195 -0
  72. package/dist/server/connections.js +615 -0
  73. package/dist/server/constraints.js +62 -0
  74. package/dist/server/credentials.js +230 -0
  75. package/dist/server/fixture.js +199 -0
  76. package/dist/server/graph.js +194 -0
  77. package/dist/server/impact.js +48 -0
  78. package/dist/server/index.js +2204 -0
  79. package/dist/server/journal.js +173 -0
  80. package/dist/server/layouts.js +128 -0
  81. package/dist/server/mcp.js +2840 -0
  82. package/dist/server/shapeonly.js +91 -0
  83. package/dist/shared/breakdown.js +231 -0
  84. package/dist/shared/breakdowntext.js +257 -0
  85. package/dist/shared/diff.js +130 -0
  86. package/dist/shared/like.js +29 -0
  87. package/dist/shared/lint.js +149 -0
  88. package/dist/shared/order.js +133 -0
  89. package/dist/shared/page.js +932 -0
  90. package/dist/shared/query.js +831 -0
  91. package/dist/shared/recordview.js +343 -0
  92. package/dist/shared/schema.js +377 -0
  93. package/dist/shared/sqlsaved.js +67 -0
  94. package/dist/shared/view.js +981 -0
  95. package/dist/shared/viewtext.js +273 -0
  96. package/dist/shared/vocabulary.js +164 -0
  97. 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
+ }