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,188 @@
1
+ /**
2
+ * A connection string, taken apart and put back together.
3
+ *
4
+ * One box asking for `postgres://user:password@host:5432/db or /path/to/file.db`
5
+ * is fine if you already know the shape and unhelpful if you do not — and the
6
+ * people most likely not to know are the ones a tool like this is for. It is
7
+ * also the box that quietly encourages the worst habit available: typing a
8
+ * password into a string that then travels wherever the string travels.
9
+ *
10
+ * So the dialog offers both, and this is the part that lets them be the same
11
+ * thing rather than two forms that disagree. Pure — no DOM, no state — so the
12
+ * fiddly half can be tested directly, which it needs to be: the fiddly half
13
+ * is percent-encoding and it is where a password containing `@` turns into a
14
+ * connection to a host that does not exist.
15
+ */
16
+
17
+ /** What each driver assumes when a port is not given. */
18
+ export const DEFAULT_PORTS = { postgres: 5432, mysql: 3306, mariadb: 3306 };
19
+
20
+ /** The drivers a connection can name, in the order the picker offers them. */
21
+ export const DRIVERS = ['postgres', 'mysql', 'mariadb', 'sqlite'];
22
+
23
+ const EMPTY = {
24
+ driver: 'postgres', host: '', port: '', database: '', user: '', password: '', params: '', file: '',
25
+ };
26
+
27
+ /**
28
+ * Take a connection string apart.
29
+ *
30
+ * A SQLite target is a bare path with no structure to read, so it comes back
31
+ * as `{ driver: 'sqlite', file }` and nothing else — pretending a file path
32
+ * has a host and a user would be inventing fields to fill boxes with.
33
+ *
34
+ * Anything unparseable comes back as the empty shape rather than throwing:
35
+ * this runs on a keystroke while someone is halfway through typing, and a
36
+ * half-typed URL is the normal state of the box, not an error.
37
+ */
38
+ export function parseConnectionUrl(url) {
39
+ const text = String(url ?? '').trim();
40
+ if (!text) return { ...EMPTY };
41
+
42
+ /* No scheme means a file, which is how SQLite is named everywhere else in
43
+ this tool. `sqlite:` is accepted because people write it. */
44
+ if (!/^[a-z][\w+.-]*:\/\//i.test(text)) {
45
+ return { ...EMPTY, driver: 'sqlite', file: text.replace(/^sqlite:(\/\/)?/i, '') };
46
+ }
47
+
48
+ let parsed;
49
+ try {
50
+ parsed = new URL(text);
51
+ } catch {
52
+ return { ...EMPTY };
53
+ }
54
+
55
+ const driver = parsed.protocol.replace(/:$/, '').toLowerCase();
56
+ if (driver === 'sqlite') return { ...EMPTY, driver: 'sqlite', file: parsed.pathname };
57
+
58
+ return {
59
+ driver: DRIVERS.includes(driver) ? driver : 'postgres',
60
+ host: parsed.hostname,
61
+ /* Blank when it is the driver's own default: a port box reading 5432 on
62
+ every Postgres connection is a box nobody reads, and one that has to be
63
+ cleared to mean "whatever the default is". */
64
+ port: parsed.port && Number(parsed.port) !== DEFAULT_PORTS[driver] ? parsed.port : '',
65
+ database: decodeURIComponent(parsed.pathname.replace(/^\//, '')),
66
+ user: decodeURIComponent(parsed.username),
67
+ password: decodeURIComponent(parsed.password),
68
+ /* Kept whole and handed back unchanged. `?sslmode=require` is not
69
+ decoration — dropping it on a round trip through this form would turn a
70
+ verified connection into a plaintext one, silently. */
71
+ params: parsed.search.replace(/^\?/, ''),
72
+ file: '',
73
+ };
74
+ }
75
+
76
+ /**
77
+ * `${VAR}` and `$VAR`, the two spellings `expandEnv` accepts.
78
+ *
79
+ * Global, because a value may be part literal and part reference —
80
+ * `prefix-${TOKEN}` is a thing people write — and the encoder below walks the
81
+ * matches rather than testing the whole string.
82
+ */
83
+ const ENV_REFERENCE = /\$\{[A-Za-z_][A-Za-z0-9_]*\}|\$[A-Za-z_][A-Za-z0-9_]*/g;
84
+
85
+ /**
86
+ * Encode a URL part, leaving any environment reference in it alone.
87
+ *
88
+ * This is the difference between a form that supports the arrangement the
89
+ * documentation recommends first and one that quietly destroys it.
90
+ * `encodeURIComponent('${PROD_PASSWORD}')` is `%24%7BPROD_PASSWORD%7D`, which
91
+ * `expandEnv` does not match — so typing a reference into the password box
92
+ * produced a connection whose password was the literal characters of the
93
+ * reference, with nothing on screen to say so.
94
+ *
95
+ * Everything that is not a reference is still encoded, because the reason
96
+ * this function exists at all is passwords containing `@` and `:`.
97
+ */
98
+ function encodePart(value) {
99
+ const text = String(value ?? '');
100
+ let out = '';
101
+ let at = 0;
102
+ for (const match of text.matchAll(ENV_REFERENCE)) {
103
+ out += encodeURIComponent(text.slice(at, match.index)) + match[0];
104
+ at = match.index + match[0].length;
105
+ }
106
+ return out + encodeURIComponent(text.slice(at));
107
+ }
108
+
109
+ /**
110
+ * Whether this is a server URL with its `://` missing.
111
+ *
112
+ * Anything without a scheme is read as a file path, because that is how
113
+ * SQLite is named everywhere in this tool — and that is right for
114
+ * `./data/app.db` and wrong for `postgres@127.0.0.1/tablewalk`, which is
115
+ * exactly what tablewalk itself prints as a connection's name. Copying what
116
+ * the app showed you and pasting it back got "unable to open database file",
117
+ * which is SQLite's honest answer to a question nobody meant to ask.
118
+ *
119
+ * An `@` before the first `/` is the tell, and a narrow one: a file path
120
+ * essentially never has one, and a `user@host` almost always does. It is
121
+ * deliberately not a guess at *which* driver — that is the one thing the
122
+ * string does not say, so it is the one thing worth asking about.
123
+ */
124
+ export function looksSchemeless(url) {
125
+ const text = String(url ?? '').trim();
126
+ if (!text || /^[a-z][\w+.-]*:\/\//i.test(text)) return false;
127
+ return /^[^/\s@]+@[^/\s@]+/.test(text);
128
+ }
129
+
130
+ /** Whether a value is, or contains, an environment reference. */
131
+ export function isEnvReference(value) {
132
+ return new RegExp(ENV_REFERENCE.source).test(String(value ?? ''));
133
+ }
134
+
135
+ /**
136
+ * Put one back together.
137
+ *
138
+ * Every part is encoded on the way in, which is the whole reason this is a
139
+ * function rather than a template string: a password of `p@ss:word` written
140
+ * straight into `user:p@ss:word@host` names the host `word@host` and the
141
+ * failure reads as "could not connect" rather than "your password has an @
142
+ * in it".
143
+ */
144
+ export function buildConnectionUrl(parts) {
145
+ const p = { ...EMPTY, ...parts };
146
+ if (p.driver === 'sqlite') return String(p.file ?? '').trim();
147
+
148
+ const host = String(p.host ?? '').trim();
149
+ if (!host) return '';
150
+
151
+ const auth = p.user
152
+ ? `${encodePart(p.user)}${p.password ? `:${encodePart(p.password)}` : ''}@`
153
+ : '';
154
+ const port = String(p.port ?? '').trim();
155
+ const database = String(p.database ?? '').trim();
156
+ const params = String(p.params ?? '').trim().replace(/^\?/, '');
157
+
158
+ return `${p.driver}://${auth}${host}${port ? `:${port}` : ''}`
159
+ + `/${encodeURIComponent(database)}`
160
+ + `${params ? `?${params}` : ''}`;
161
+ }
162
+
163
+ /**
164
+ * The same string with the password replaced by dots.
165
+ *
166
+ * For the preview, which exists so someone can check the URL they are about
167
+ * to save — and checking it should not require the secret to be sitting on
168
+ * screen where a shoulder, a screen share or a screenshot can reach it. The
169
+ * dots are a fixed width so the length is not a hint either.
170
+ */
171
+ export function redactConnectionUrl(url) {
172
+ const text = String(url ?? '');
173
+ try {
174
+ const parsed = new URL(text);
175
+ if (!parsed.password) return text;
176
+ /* A reference is not a secret. `${PROD_PASSWORD}` names a variable and
177
+ reveals nothing, and it is the one part of the URL worth reading back
178
+ carefully — hiding it would take away the only way to notice the
179
+ variable name is misspelled. */
180
+ if (isEnvReference(decodeURIComponent(parsed.password))) return text;
181
+ parsed.password = '';
182
+ /* Rebuilt by hand rather than by assigning a mask to `parsed.password`,
183
+ which would percent-encode the dots into `%E2%80%A2` and show that. */
184
+ return parsed.toString().replace(`${parsed.username}@`, `${parsed.username}:••••••@`);
185
+ } catch {
186
+ return text;
187
+ }
188
+ }