@bakery-framework/plugin-db-explorer 2.0.0-alpha.9 → 2.0.0-rc.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 (49) hide show
  1. package/package.json +5 -5
  2. package/src/access.ts +53 -17
  3. package/src/client/api.ts +48 -6
  4. package/src/client/bulk.ts +31 -7
  5. package/src/client/cell.ts +5 -5
  6. package/src/client/confirm.ts +11 -12
  7. package/src/client/csv-commit.ts +25 -5
  8. package/src/client/csv-map.ts +7 -7
  9. package/src/client/csv-model.ts +61 -8
  10. package/src/client/csv-pick.ts +1 -1
  11. package/src/client/csv-preview.ts +2 -2
  12. package/src/client/csv.ts +2 -2
  13. package/src/client/dom.ts +3 -5
  14. package/src/client/edit-session.ts +7 -7
  15. package/src/client/editors.ts +5 -5
  16. package/src/client/filter-builder.ts +6 -6
  17. package/src/client/fk.ts +9 -9
  18. package/src/client/grid-body.ts +4 -4
  19. package/src/client/grid-header.ts +2 -2
  20. package/src/client/grid-rowbar.ts +1 -1
  21. package/src/client/grid.ts +8 -9
  22. package/src/client/meta.ts +6 -6
  23. package/src/client/page.ts +4 -4
  24. package/src/client/panel.ts +8 -8
  25. package/src/client/relations.ts +4 -4
  26. package/src/client/save.ts +4 -4
  27. package/src/client/sidebar.ts +6 -6
  28. package/src/client/state.ts +4 -4
  29. package/src/client/statusbar.ts +6 -6
  30. package/src/client/structure.ts +8 -8
  31. package/src/client/tabs.ts +10 -10
  32. package/src/client/tabstrip.ts +3 -3
  33. package/src/client.ts +17 -17
  34. package/src/endpoints/common.ts +25 -6
  35. package/src/endpoints/graph.ts +5 -5
  36. package/src/endpoints/import.ts +5 -5
  37. package/src/endpoints/read.ts +70 -10
  38. package/src/endpoints/rows.ts +219 -14
  39. package/src/identity.ts +122 -52
  40. package/src/index.ts +1 -1
  41. package/src/policy.ts +4 -4
  42. package/src/preview.ts +5 -5
  43. package/src/setup.ts +16 -10
  44. package/src/shared/coerce.ts +21 -22
  45. package/src/shared/csv.ts +5 -5
  46. package/src/shared/filters.ts +6 -6
  47. package/src/shared/plan.ts +7 -7
  48. package/src/shell.ts +3 -3
  49. package/src/validate.ts +10 -10
package/src/shared/csv.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * CSV, RFC 4180-ish, with the delimiter sniffed rather than assumed.
3
3
  *
4
- * **Pure** — see the note at the top of `coerce.ts` — and, unlike the rest of
4
+ * **Pure**. See the note at the top of `coerce.ts`, and, unlike the rest of
5
5
  * `shared/`, **browser-only**. It was written when the server re-parsed the
6
6
  * text it was sent; `endpoints/import.ts` takes records that are already mapped
7
7
  * and coerced, so no CSV text ever reaches it. `client/csv-model.ts` is the
@@ -19,7 +19,7 @@
19
19
  * `if (c === '"') inQuotes = true` with no check that the field is empty.
20
20
  * RFC 4180 only gives `"` meaning at the start of a field.
21
21
  * 2. **No BOM handling.** A file saved by Excel begins ``, so the first
22
- * header comes back as `id` and matches no column — the single most
22
+ * header comes back as `id` and matches no column: the single most
23
23
  * common import failure there is.
24
24
  * 3. Comma only. A European export is `;`-delimited and a database dump is
25
25
  * often tab-delimited; both parse as one column per row and then fail with
@@ -28,7 +28,7 @@
28
28
  * (`row.length === 1 && row[0] === ''`), which also drops a legitimate
29
29
  * single-column row whose value is empty.
30
30
  *
31
- * Fixing it in place would change `importCSV`'s behaviour for every existing
31
+ * Fixing it in place would change `importCSV`'s behavior for every existing
32
32
  * caller of the ORM, which is a separate decision from adding an importer to
33
33
  * the explorer. This parser is the explorer's; if the ORM's is ever replaced,
34
34
  * this is the implementation to move.
@@ -112,7 +112,7 @@ function countPerLine(text: string, delimiter: string, max: number): number[] {
112
112
  * No trimming and no type guessing: `"01"` and `01` are both the three-, then
113
113
  * two-character strings they look like, and turning either into a number is
114
114
  * `coerce.ts`'s job once a column is known. A field is only ever `''` when the
115
- * file said so — this parser has no concept of NULL.
115
+ * file said so: this parser has no concept of NULL.
116
116
  */
117
117
  export function parseCSVRows(text: string, delimiter = ','): string[][] {
118
118
  const csv = stripBOM(text)
@@ -137,7 +137,7 @@ export function parseCSVRows(text: string, delimiter = ','): string[][] {
137
137
  endField()
138
138
  // A blank line is not a row: it is what a trailing newline, or a stray one
139
139
  // in the middle of a file, leaves behind. One field, empty, and never
140
- // quoted is the only shape it can take — a line reading `""` is a real row
140
+ // quoted is the only shape it can take: a line reading `""` is a real row
141
141
  // holding one empty value, which is why `rowQuoted` is tracked.
142
142
  const blank = row.length === 1 && row[0] === '' && !rowQuoted
143
143
  if (!blank) rows.push(row)
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * The filter vocabulary, written once and read by both halves.
3
3
  *
4
- * **Pure, and deliberately in `shared/`** — the client builds filters and the
4
+ * **Pure, and deliberately in `shared/`**: the client builds filters and the
5
5
  * endpoint validates them, and a vocabulary that lived on only one side would
6
6
  * drift. Like the rest of `shared/`, this is compiled into the browser bundle,
7
7
  * so it imports nothing from `@bakery-framework/*` (see `client/safety.test.ts`).
@@ -14,7 +14,7 @@
14
14
  *
15
15
  * This is that caller. Validating matters more than it looks: a *dropped*
16
16
  * filter widens the result set, so a typo'd operator would silently show more
17
- * rows than were asked for — and the explorer's Delete acts on a selection made
17
+ * rows than were asked for, and the explorer's Delete acts on a selection made
18
18
  * from exactly that view. Failing the request is the only safe direction
19
19
  * (convention 2).
20
20
  */
@@ -51,7 +51,7 @@ export const FILTER_OPS: readonly FilterOp[] = [
51
51
  * The two operators that bind nothing.
52
52
  *
53
53
  * `IS NULL` has no parameter, which is the whole reason a filter cannot be
54
- * modelled as a plain column/value pair — the value input is *hidden* for
54
+ * modeled as a plain column/value pair: the value input is *hidden* for
55
55
  * these rather than ignored, because an input whose contents do nothing is a
56
56
  * lie about what the query will do.
57
57
  */
@@ -107,7 +107,7 @@ export function filter(
107
107
  /**
108
108
  * Filters as `getData` wants them: keyed by column.
109
109
  *
110
- * **The wire shape is a record, so a column can carry only one filter** — the
110
+ * **The wire shape is a record, so a column can carry only one filter**: the
111
111
  * ORM iterates `Object.entries(options.filters)`. Two chips on one column
112
112
  * therefore cannot both be sent, and the last one wins rather than the first,
113
113
  * because the last is the one the user just touched. `duplicateColumns` exists
@@ -132,7 +132,7 @@ export function toWire(filters: readonly Filter[]): Record<string, WireFilter> {
132
132
  return wire
133
133
  }
134
134
 
135
- /** Columns named by more than one filter — the ones the wire cannot carry. */
135
+ /** Columns named by more than one filter: the ones the wire cannot carry. */
136
136
  export function duplicateColumns(filters: readonly Filter[]): string[] {
137
137
  const seen = new Set<string>()
138
138
  const twice = new Set<string>()
@@ -189,7 +189,7 @@ function parseOne(
189
189
  const { op, value: operand } = value as { op?: unknown; value?: unknown }
190
190
  if (!isFilterOp(op)) {
191
191
  return {
192
- error: `unknown filter operator ${JSON.stringify(op)} on ${column} — expected one of ${FILTER_OPS.join(', ')}`,
192
+ error: `unknown filter operator ${JSON.stringify(op)} on ${column}: expected one of ${FILTER_OPS.join(', ')}`,
193
193
  }
194
194
  }
195
195
  if (!opTakesValue(op)) return { filter: { op } }
@@ -2,7 +2,7 @@
2
2
  * Turning a spreadsheet into statements: which CSV column feeds which database
3
3
  * column, what stops an import before it starts, and what one row edit changes.
4
4
  *
5
- * **Pure** — see the note at the top of `coerce.ts`. The import dialog runs
5
+ * **Pure**. See the note at the top of `coerce.ts`. The import dialog runs
6
6
  * `autoMap` and `blockingIssues` in the browser to show the mapping before
7
7
  * anything is sent, and the server runs the same two functions on what arrives,
8
8
  * because a client-side check is a convenience and never a guarantee.
@@ -27,7 +27,7 @@ export type ColumnMapping = Record<string, string | null>
27
27
  * Nothing fuzzier: a near-miss that silently loads the wrong column is worse
28
28
  * than an unmapped one the dialog can ask about.
29
29
  *
30
- * A database column is claimed at most once. Two headers that normalise to the
30
+ * A database column is claimed at most once. Two headers that normalize to the
31
31
  * same name would otherwise both map to it, and the second would quietly win.
32
32
  */
33
33
  export function autoMap(
@@ -42,7 +42,7 @@ export function autoMap(
42
42
  for (const c of dbColumns) {
43
43
  const key = normalize(c)
44
44
  // First declaration wins, so the mapping does not depend on column order
45
- // among columns that normalise alike.
45
+ // among columns that normalize alike.
46
46
  if (!byNormal.has(key)) byNormal.set(key, c)
47
47
  }
48
48
 
@@ -76,7 +76,7 @@ export interface PlanIssue {
76
76
  /**
77
77
  * What makes this mapping unrunnable, as opposed to merely lossy.
78
78
  *
79
- * A column left out of the mapping is fine when the database can fill it in —
79
+ * A column left out of the mapping is fine when the database can fill it in:
80
80
  * an auto-increment key, a default, or a nullable column. A NOT NULL column
81
81
  * with no default and no mapping is not: every row would fail at the database,
82
82
  * one at a time, after the import had already started.
@@ -123,7 +123,7 @@ export function blockingIssues(
123
123
  export interface UpdatePlanInput {
124
124
  /** The row as the editor last saw it, keyed by raw column name. */
125
125
  original: Record<string, unknown>
126
- /** Edited values. **An absent key means unchanged** — see `coerce.ts`. */
126
+ /** Edited values. **An absent key means unchanged**. See `coerce.ts`. */
127
127
  edits: Record<string, unknown>
128
128
  /** The identity columns, from `describeIdentity`. */
129
129
  identity: readonly string[]
@@ -136,7 +136,7 @@ export interface UpdatePlanResult {
136
136
  where: Record<string, unknown>
137
137
  /** Edited keys that matched what was already there. */
138
138
  unchanged: string[]
139
- /** Identity columns `original` does not carry — the plan is unusable. */
139
+ /** Identity columns `original` does not carry: the plan is unusable. */
140
140
  missingIdentity: string[]
141
141
  }
142
142
 
@@ -148,7 +148,7 @@ export interface UpdatePlanResult {
148
148
  * and set the new one, and a plan built from the edited row would look for a
149
149
  * row that does not exist yet.
150
150
  *
151
- * Unchanged columns are dropped from `set`. That is not only economy — a `set`
151
+ * Unchanged columns are dropped from `set`. That is not only economy: a `set`
152
152
  * carrying every column makes two people editing different columns of the same
153
153
  * row collide, and the whole reason to send a narrow statement is that they
154
154
  * should not.
package/src/shell.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  * stylesheet in the middle of it is a route table nobody reads. `setup.ts`
7
7
  * keeps the routing and the CSRF policy; this keeps the paint.
8
8
  *
9
- * There is **no markup generated from data here** — the body is an empty
9
+ * There is **no markup generated from data here**: the body is an empty
10
10
  * `#app` and a module script. Every value the user sees is written by
11
11
  * `client/dom.ts` through `textContent`, which is why this plugin has no XSS
12
12
  * surface even though it renders arbitrary row contents.
@@ -53,7 +53,7 @@ const CSS = `
53
53
  .tab-new { font: inherit; background: none; border: 0; color: var(--dim); cursor: pointer; padding: .3rem .8rem; }
54
54
  .tab-new:hover { color: var(--accent-text); }
55
55
 
56
- /* Data / Structure / Relations — one level of nesting, and only one. */
56
+ /* Data / Structure / Relations: one level of nesting, and only one. */
57
57
  .viewtabs { display: flex; gap: .2rem; padding: .3rem .8rem 0; border-bottom: 1px solid var(--line); background: var(--bg); }
58
58
  .viewtab { font: inherit; font-size: .8rem; background: none; border: 0; border-bottom: 2px solid transparent; color: var(--dim); padding: .3rem .7rem; cursor: pointer; }
59
59
  .viewtab:hover { color: var(--text); }
@@ -140,7 +140,7 @@ const CSS = `
140
140
  dialog h4 { margin: .8rem 0 .3rem; font-size: .85rem; color: var(--dim); }
141
141
  .sel { font: inherit; background: var(--bg); color: var(--text); border: 1px solid var(--line); border-radius: 4px; padding: .15rem .3rem; }
142
142
 
143
- /* The row side panel — where JSON, long text and both graph directions live. */
143
+ /* The row side panel: where JSON, long text and both graph directions live. */
144
144
  .panel { position: fixed; top: 0; right: 0; width: min(38rem, 100vw); height: 100vh; overflow-y: auto; background: var(--panel); border-left: 1px solid var(--line); padding: 1rem; z-index: 30; }
145
145
  .panel-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 1rem; }
146
146
  .panel-title h3 { margin: 0; font-family: ui-monospace, monospace; }
package/src/validate.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * Two rules run through every function here.
6
6
  *
7
- * **Errors accumulate.** A 400 names every bad field, not the first one — a
7
+ * **Errors accumulate.** A 400 names every bad field, not the first one: a
8
8
  * fifty-row paste fixed one error per round trip is fifty round trips, and the
9
9
  * user cannot see the shape of their mistake.
10
10
  *
@@ -55,7 +55,7 @@ const error = (
55
55
  /**
56
56
  * Resolve a wire column name against the table.
57
57
  *
58
- * Accepts the raw database name, and the camel spelling as a courtesy — the
58
+ * Accepts the raw database name, and the camel spelling as a courtesy: the
59
59
  * grid renders raw names, but a script written against a typed schema has camel
60
60
  * ones and the two are the same column. Anything else is `unknown_column`.
61
61
  */
@@ -85,7 +85,7 @@ function coerceInto(
85
85
  * is fine when the database can fill it in (auto-increment, a default, or
86
86
  * nullable) and is a `required` error otherwise. Refusing here rather than at
87
87
  * the database is what keeps a 1,000-row insert from failing on row 700 with
88
- * 699 rows already written — the whole statement is known to be well-formed
88
+ * 699 rows already written: the whole statement is known to be well-formed
89
89
  * before any of it runs.
90
90
  */
91
91
  export function validateInsertRow(
@@ -135,8 +135,8 @@ export interface PartialOptions {
135
135
  * Whether a `json` or `buffer` column may appear.
136
136
  *
137
137
  * `false` for `expect`, where the answer is not "be careful" but "this cannot
138
- * be expressed": those two kinds have no portable equality predicate — see
139
- * `comparableKind` — so an `expect` over one is a condition that never
138
+ * be expressed": those two kinds have no portable equality predicate (see
139
+ * `comparableKind`), so an `expect` over one is a condition that never
140
140
  * matches, which reads to the caller as a permanent conflict.
141
141
  */
142
142
  allowUncomparable: boolean
@@ -145,7 +145,7 @@ export interface PartialOptions {
145
145
  }
146
146
 
147
147
  /**
148
- * A partial row — the `set` of an update, or the `expect` it is guarded by.
148
+ * A partial row: the `set` of an update, or the `expect` it is guarded by.
149
149
  *
150
150
  * Absent keys are absent, and that is the point: this function has no concept
151
151
  * of a default, because "the caller did not mention this column" means "leave
@@ -197,7 +197,7 @@ export function validatePartial(
197
197
  * The columns of `set` that may only be written with `force`.
198
198
  *
199
199
  * A `json` or `buffer` column cannot appear in `expect`, so an edit to one is
200
- * unguarded by construction — it overwrites whatever is there, including
200
+ * unguarded by construction: it overwrites whatever is there, including
201
201
  * whatever somebody else wrote a moment ago. `force` is how the caller says
202
202
  * they know that. It is not a permission; it is an acknowledgement.
203
203
  */
@@ -220,15 +220,15 @@ export interface ValidatedKey {
220
220
  /**
221
221
  * A wire key, against the table's identity.
222
222
  *
223
- * **The column set must be exactly equal** — not a subset, not a superset. A
223
+ * **The column set must be exactly equal**, not a subset, not a superset. A
224
224
  * subset is a predicate that matches more than one row, which for a composite
225
225
  * key is precisely the dashboard's bug (its MySQL path addresses rows by the
226
226
  * *first* primary-key column, so one edit rewrites every row sharing it). A
227
227
  * superset is a caller who believes the key contains something it does not, and
228
228
  * silently ignoring the extra column would confirm that belief.
229
229
  *
230
- * The key is transparent by design — `{ col: value }` rather than an opaque
231
- * token — so there is no server-side key cache to keep, and nothing that can go
230
+ * The key is transparent by design (`{ col: value }` rather than an opaque
231
+ * token), so there is no server-side key cache to keep, and nothing that can go
232
232
  * stale between rendering a page and saving a row (convention 6).
233
233
  */
234
234
  export function validateKey(