@standardbeagle/edit-db 0.4.484 → 0.4.581

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 (111) hide show
  1. package/dist/common/fetcher.d.ts +7 -2
  2. package/dist/common/saved-objects.d.ts +40 -0
  3. package/dist/common/schema-queries.d.ts +1 -0
  4. package/dist/components/bulk-edit-dialog.d.ts +18 -0
  5. package/dist/components/content-panel.d.ts +5 -2
  6. package/dist/components/content-viewer.d.ts +8 -1
  7. package/dist/components/content-viewer.test.d.ts +1 -0
  8. package/dist/components/data-table.d.ts +49 -2
  9. package/dist/components/detail-panel.d.ts +2 -2
  10. package/dist/components/export-button.d.ts +19 -0
  11. package/dist/components/export-button.test.d.ts +1 -0
  12. package/dist/components/filters/fk-filter.d.ts +2 -0
  13. package/dist/components/filters/fk-filter.test.d.ts +1 -0
  14. package/dist/components/filters/number-filter.d.ts +8 -0
  15. package/dist/components/filters/number-filter.test.d.ts +1 -0
  16. package/dist/components/fk-cell-popover.d.ts +12 -5
  17. package/dist/components/fk-cell-popover.test.d.ts +1 -0
  18. package/dist/components/form-runner-host.d.ts +14 -0
  19. package/dist/components/form-runner.d.ts +43 -0
  20. package/dist/components/form-runner.test.d.ts +0 -0
  21. package/dist/components/form-subform.d.ts +11 -0
  22. package/dist/components/form-subform.test.d.ts +0 -0
  23. package/dist/components/grouped-grid-summary.d.ts +17 -0
  24. package/dist/components/grouped-grid-summary.test.d.ts +0 -0
  25. package/dist/components/m2m-panel.d.ts +4 -5
  26. package/dist/components/ui/table.test.d.ts +1 -0
  27. package/dist/data-edit.d.ts +25 -4
  28. package/dist/data-panel.d.ts +0 -11
  29. package/dist/edit-db.css +1 -1
  30. package/dist/editor.d.ts +22 -1
  31. package/dist/editor.es.js +7368 -4589
  32. package/dist/editor.stories.d.ts +0 -1
  33. package/dist/editor.umd.cjs +48 -27
  34. package/dist/hooks/useColumnNav.d.ts +3 -3
  35. package/dist/hooks/useColumnSizingPersistence.d.ts +19 -0
  36. package/dist/hooks/useColumnVisibilityPersistence.d.ts +34 -0
  37. package/dist/hooks/useColumnVisibilityPersistence.test.d.ts +1 -0
  38. package/dist/hooks/useCompositeTableRef.d.ts +20 -2
  39. package/dist/hooks/useCompositeTableRef.test.d.ts +1 -0
  40. package/dist/hooks/useDataTable.d.ts +59 -6
  41. package/dist/hooks/useDebouncedCommit.d.ts +19 -0
  42. package/dist/hooks/useDebouncedCommit.test.d.ts +1 -0
  43. package/dist/hooks/useDeltaMutation.d.ts +20 -0
  44. package/dist/hooks/useDeltaMutation.test.d.ts +0 -0
  45. package/dist/hooks/useEditorConfig.d.ts +27 -0
  46. package/dist/hooks/useFitMode.d.ts +23 -0
  47. package/dist/hooks/useFitMode.test.d.ts +1 -0
  48. package/dist/hooks/usePanelSize.d.ts +28 -0
  49. package/dist/hooks/usePanelSize.test.d.ts +1 -0
  50. package/dist/hooks/usePath.d.ts +2 -5
  51. package/dist/hooks/useRowHoverActions.d.ts +26 -0
  52. package/dist/hooks/useSchema.d.ts +8 -2
  53. package/dist/hooks/useSchemaFilter.test.d.ts +1 -0
  54. package/dist/hooks/useTableExport.d.ts +13 -0
  55. package/dist/hooks/useTableMutation.d.ts +1 -4
  56. package/dist/hooks/useTableMutation.test.d.ts +0 -0
  57. package/dist/hooks/useTableRef.d.ts +23 -2
  58. package/dist/hooks/useTableRef.test.d.ts +1 -0
  59. package/dist/hooks/useTableStats.d.ts +7 -24
  60. package/dist/hooks/useToast.d.ts +15 -0
  61. package/dist/index.d.ts +13 -1
  62. package/dist/lib/column-order.d.ts +17 -0
  63. package/dist/lib/column-order.test.d.ts +1 -0
  64. package/dist/lib/content-detect.d.ts +39 -0
  65. package/dist/lib/content-detect.sniff.test.d.ts +1 -0
  66. package/dist/lib/date-input.d.ts +21 -0
  67. package/dist/lib/date-input.test.d.ts +1 -0
  68. package/dist/lib/drill-stack.d.ts +19 -2
  69. package/dist/lib/export.d.ts +122 -0
  70. package/dist/lib/export.test.d.ts +1 -0
  71. package/dist/lib/field-validation.d.ts +16 -0
  72. package/dist/lib/fk.d.ts +23 -2
  73. package/dist/lib/form-definition.d.ts +57 -0
  74. package/dist/lib/form-definition.test.d.ts +1 -0
  75. package/dist/lib/form-runner-nav.d.ts +19 -0
  76. package/dist/lib/form-runner-nav.test.d.ts +1 -0
  77. package/dist/lib/form-subform.d.ts +12 -0
  78. package/dist/lib/form-subform.test.d.ts +1 -0
  79. package/dist/lib/form-widget.d.ts +36 -0
  80. package/dist/lib/form-widget.test.d.ts +1 -0
  81. package/dist/lib/format-value.d.ts +26 -0
  82. package/dist/lib/grid-grouping.d.ts +63 -0
  83. package/dist/lib/grid-grouping.test.d.ts +1 -0
  84. package/dist/lib/invalidate.d.ts +7 -0
  85. package/dist/lib/invalidate.test.d.ts +1 -0
  86. package/dist/lib/label-match.d.ts +13 -0
  87. package/dist/lib/label-match.test.d.ts +1 -0
  88. package/dist/lib/m2m.d.ts +45 -6
  89. package/dist/lib/mutation-payload.d.ts +37 -0
  90. package/dist/lib/mutation-payload.test.d.ts +1 -0
  91. package/dist/lib/query-builder.d.ts +137 -8
  92. package/dist/lib/row-id.d.ts +20 -0
  93. package/dist/lib/rows-by-pk-query.test.d.ts +1 -0
  94. package/dist/lib/scalar-types.d.ts +30 -0
  95. package/dist/lib/scalar-types.test.d.ts +1 -0
  96. package/dist/lib/table-action.d.ts +24 -0
  97. package/dist/lib/table-ref.d.ts +45 -0
  98. package/dist/lib/table-ref.test.d.ts +1 -0
  99. package/dist/{tableList.d.ts → table-list.d.ts} +1 -1
  100. package/dist/table-list.test.d.ts +0 -0
  101. package/dist/table-view.chart.test.d.ts +1 -0
  102. package/dist/table-view.d.ts +22 -0
  103. package/dist/table-view.test.d.ts +0 -0
  104. package/dist/types/schema.d.ts +54 -10
  105. package/package.json +2 -4
  106. package/dist/common/schema.d.ts +0 -2
  107. package/dist/data-data-table.d.ts +0 -17
  108. package/dist/error-panel.d.ts +0 -1
  109. /package/dist/common/{schema.test.d.ts → saved-objects.test.d.ts} +0 -0
  110. /package/dist/{components/table-dimensions.test.d.ts → common/schema-queries.test.d.ts} +0 -0
  111. /package/dist/{tableList.test.d.ts → components/bulk-edit-dialog.test.d.ts} +0 -0
@@ -0,0 +1,37 @@
1
+ import { Column, Table } from '../types/schema';
2
+ import { PkFilter } from './row-id';
3
+ export declare function coerceNumericValue(value: unknown, paramType: string, columnName: string): number;
4
+ export interface ColumnJoin {
5
+ column: Column;
6
+ }
7
+ /**
8
+ * Coerces a form/grid detail object into a wire-ready mutation payload: numeric
9
+ * columns become numbers, BigInt/Decimal stay strings (precision beyond 2^53),
10
+ * booleans and JSON columns get their scalar shapes, empty values follow the
11
+ * insert-vs-update semantics (omit so DB defaults apply vs null to clear), and —
12
+ * for updates — the primary-key columns are overwritten from the resolved
13
+ * <paramref>pkFilter</paramref> so a payload can never retarget another row.
14
+ * Shared by the single-row hook and the bulk delta paths.
15
+ */
16
+ export declare function coerceDetail(detail: Record<string, unknown>, editColumns: ColumnJoin[], idColumns: Column[], pkFilter: PkFilter | null, isInsert: boolean): Record<string, unknown>;
17
+ /**
18
+ * Builds the `updated:` payload list for a bulk edit: for each FRESH row
19
+ * snapshot, echo every write-set column from the snapshot (the server's
20
+ * `Update_<t>` input requires all non-nullable columns), overlay the shared
21
+ * change set, validate, and coerce — with the primary key taken from the
22
+ * snapshot itself, composite-safe, so a payload can never retarget another row.
23
+ * Throws (never silently skips) when a row's key cannot be resolved or a merged
24
+ * payload fails validation: a bulk edit either stages every selected row or none.
25
+ */
26
+ export declare function buildBulkUpdatePayloads(table: Pick<Table, 'primaryKeys'>, writeColumns: Column[], idColumns: Column[], freshRows: readonly Record<string, unknown>[], changes: Record<string, unknown>): Record<string, unknown>[];
27
+ /**
28
+ * Builds the `updated:` payload list for STAGED inline cell edits: unlike the
29
+ * shared-change bulk edit, each row carries its OWN change map. Per fresh row:
30
+ * the write set is every non-nullable editable column plus that row's changed
31
+ * columns (the server's `Update_<t>` input requires the non-nullable echo), the
32
+ * snapshot fills the echo, the row's changes overlay it, and the primary key
33
+ * comes from the snapshot itself. Throws — never skips — when a fetched row has
34
+ * no staged changes or a merged payload fails validation: Save all either
35
+ * stages every edited row or none.
36
+ */
37
+ export declare function buildStagedUpdatePayloads(table: Pick<Table, 'primaryKeys'>, editableColumns: Column[], idColumns: Column[], freshRows: readonly Record<string, unknown>[], changesForRow: (row: Record<string, unknown>) => Record<string, unknown> | undefined): Record<string, unknown>[];
@@ -0,0 +1 @@
1
+ export {};
@@ -1,9 +1,17 @@
1
- import { Table, Schema } from '../types/schema';
1
+ import { Table, Column, Join, SchemaContextValue } from '../types/schema';
2
2
  import { ColumnFiltersState } from '@tanstack/react-table';
3
+ import { PkEqFilterResult, PkFilter } from './row-id';
3
4
  export interface FilterResult {
4
5
  variables: Record<string, unknown>;
5
6
  param: string;
6
7
  filterText: string;
8
+ /**
9
+ * Non-null when the filter could not be built. The predicate is NOT part of
10
+ * the query, so the caller MUST refuse to run it rather than showing rows the
11
+ * filter was supposed to exclude. `null` with an empty `filterText` means
12
+ * "no filter requested", which is a different thing entirely.
13
+ */
14
+ error: string | null;
7
15
  }
8
16
  export interface ColumnFilterValue {
9
17
  operator: string;
@@ -13,6 +21,12 @@ export interface ColumnFilterResult {
13
21
  variables: Record<string, unknown>;
14
22
  params: string[];
15
23
  filterTexts: string[];
24
+ /**
25
+ * One message per column filter that could not be turned into a predicate.
26
+ * Non-empty means the built predicate is NARROWER than what the UI is showing
27
+ * as active, so the caller must refuse the query instead of returning rows.
28
+ */
29
+ errors: string[];
16
30
  }
17
31
  export interface PkTypeInfo {
18
32
  name: string;
@@ -22,12 +36,65 @@ interface RowData {
22
36
  id?: number | string | null;
23
37
  [key: string]: unknown;
24
38
  }
39
+ export declare function isGraphQlName(value: unknown): value is string;
40
+ export declare function assertGraphQlName(value: unknown, kind: string): asserts value is string;
25
41
  export declare function getFilterOperators(paramType: string): string[];
26
- export declare function getFilterObj(filterString: string): FilterResult;
27
- export declare function toLocaleDate(d: string): string;
28
- export declare function getRowPkValue(row: RowData, table: Table): string;
42
+ /**
43
+ * Parses the header filter (a JSON `[column, operator, value, type]` tuple carried
44
+ * in the `filter` URL param) into a GraphQL predicate.
45
+ *
46
+ * A rejected filter reports an error rather than degrading to "no filter". Silently
47
+ * dropping it left the grid showing the UNFILTERED table while the filter UI still
48
+ * advertised the predicate as active — and select-all + Delete then operated over a
49
+ * result set that was not the one the user believed they had narrowed to.
50
+ */
51
+ export declare function parseTableFilterString(filterString: string): FilterResult;
52
+ export declare function getRowPkValue(row: RowData, table: Table, rowIndex?: number): string;
53
+ /**
54
+ * The GraphQL type a filter/lookup VARIABLE must be declared as for a column of
55
+ * this paramType. It has to be the column's own scalar: the variable is used in a
56
+ * `FilterType<T>Input` field position, and GraphQL rejects any variable whose
57
+ * declared type is not that exact scalar — "Variable '$cf_x_0' of type 'String'
58
+ * used in position expecting type 'DateTime'" fails the WHOLE document, so one
59
+ * mistyped column empties the grid rather than degrading that one predicate.
60
+ *
61
+ * BigInt/Decimal values travel as decimal STRINGS (coerceForGql's default branch
62
+ * stringifies): a JSON number is a double, which rounds a bigint key past 2^53 and
63
+ * drops exact-decimal precision. The server's BigInt/Decimal scalars accept the
64
+ * string form for exactly this reason — see ExactNumericScalars.cs.
65
+ */
29
66
  export declare function getGraphQlType(paramType: string): string;
67
+ /**
68
+ * Widens a date-only bound ("2024-02-01") to the instant at the requested edge of
69
+ * that LOCAL day, in the ISO-8601 form the server's DateTime/DateTimeOffset scalars
70
+ * require.
71
+ *
72
+ * The date filter's control is `<input type="date">`, so its value carries no time
73
+ * — but the column it filters is a timestamp. Sent verbatim the value is not
74
+ * ISO-8601-complete and the scalar rejects it outright; sent as midnight it silently
75
+ * means "the first instant of the day", so "on or before 1 Feb" excludes everything
76
+ * recorded ON 1 Feb. Each operator therefore takes the edge that makes it true for
77
+ * the whole day.
78
+ */
79
+ export declare function dayBoundary(date: string, edge: "start" | "end", gqlType: string, offsetMinutes?: number): string;
80
+ /**
81
+ * The wire form of one column filter: the operator actually emitted and its
82
+ * value(s). Everything passes through unchanged except a date-only bound on a
83
+ * timestamp column, where the operator may widen to a day RANGE (`_eq` on a
84
+ * timestamp column matches nothing otherwise — no row is recorded at exactly
85
+ * midnight).
86
+ */
87
+ export declare function toWireFilter(operator: string, value: unknown, gqlType: string, offsetMinutes?: number): {
88
+ operator: string;
89
+ value: unknown;
90
+ };
30
91
  export declare function buildColumnFilters(columnFilters: ColumnFiltersState, table: Table): ColumnFilterResult;
92
+ /**
93
+ * Every reason the requested filters could not be fully applied, header filter
94
+ * first. Empty means the built predicate matches exactly what the filter UI is
95
+ * advertising, which is the only condition under which the grid query may run.
96
+ */
97
+ export declare function collectFilterErrors(table: Table, filterString: string, columnFilters: ColumnFiltersState): string[];
31
98
  export declare function serializeColumnFilters(columnFilters: ColumnFiltersState): string;
32
99
  export declare function deserializeColumnFilters(raw: string): ColumnFiltersState;
33
100
  /**
@@ -39,6 +106,17 @@ export declare function getPkType(table: Table): string;
39
106
  * Empty array if the table has no primary keys.
40
107
  */
41
108
  export declare function getPkTypes(table: Table): PkTypeInfo[];
109
+ /**
110
+ * Picks the best index-served sort column for a table with no usable primary
111
+ * key: the leading key column of the clustered index if any (it IS the row
112
+ * order — sorting by it is free), else of a unique index, else of any index.
113
+ * Only sortable columns qualify — an index leading on a LOB/JSON column can't
114
+ * drive the grid's ORDER BY. Returns null when no index leads on a sortable,
115
+ * visible column; the caller falls back to its positional heuristic. Sorting a
116
+ * large table by an unindexed column re-sorts every row on every page turn, so
117
+ * this choice is a correctness-of-experience matter, not a micro-optimization.
118
+ */
119
+ export declare function pickIndexedSortColumn(table: Table, isSortable?: (col: Column) => boolean): string | null;
42
120
  /**
43
121
  * Builds the variables dict that accompanies a buildQuery result for a single-record lookup.
44
122
  * - Single PK: returns `{ id: <coerced value> }` matching the `$id` variable in the query.
@@ -48,17 +126,68 @@ export declare function getPkTypes(table: Table): PkTypeInfo[];
48
126
  * it is a route-encoded string produced by {@link encodePkRoute}.
49
127
  */
50
128
  export declare function buildPkEqVariables(idRoute: string, table: Table): Record<string, unknown>;
51
- export declare function buildQuery(table: Table, schema: Schema, filterString: string, columnFilters: ColumnFiltersState, id?: string, tableFilter?: string, filterColumn?: string): string | null;
129
+ /**
130
+ * Builds a single-row lookup query keyed by a primary-key equality filter
131
+ * (produced by {@link buildPkEqFilter}). The row comes back under the `value`
132
+ * alias: `{ value: { data: [row] } }`. Used by the content panel to re-read a
133
+ * row fresh before echoing it back in an update.
134
+ */
135
+ export declare function buildSingleRowQuery(table: Pick<Table, 'name'>, pkEq: Pick<PkEqFilterResult, 'filterText' | 'params'>, fields: readonly string[]): string;
136
+ /**
137
+ * Builds ONE query fetching fresh snapshots of several rows by primary key —
138
+ * `{or: [pkEq, pkEq, ...]}` — so a bulk edit can echo current server state for
139
+ * every selected row in a single round trip (the multi-row analogue of
140
+ * {@link buildSingleRowQuery}'s fresh re-read). Rows come back under the `value`
141
+ * alias. Every key column of every row participates (composite-safe), values
142
+ * coerce through the same GraphQL-type rules as single-row PK filters (BigInt
143
+ * keys stay strings), and identifiers are asserted before any text is built.
144
+ */
145
+ export declare function buildRowsByPkQuery(table: Pick<Table, 'name' | 'primaryKeys' | 'columns'>, pks: readonly PkFilter[], fields: readonly string[]): {
146
+ query: string;
147
+ variables: Record<string, unknown>;
148
+ } | null;
149
+ /**
150
+ * Columns included in a full-data export: every column EXCEPT binary payloads
151
+ * (non-PK), which have no useful CSV/JSON representation. Long-text columns ARE
152
+ * included — export means the full data, not the grid's preview projection.
153
+ * Single source of truth for export headers, fields, and the export query's
154
+ * SELECT list so the three cannot drift.
155
+ */
156
+ export declare function exportableColumns(table: Table): Column[];
157
+ export interface BuildQueryOptions {
158
+ /**
159
+ * `grid` (default): the on-screen projection — large values excluded, FK label
160
+ * blocks and multi-join count/preview blocks included.
161
+ * `export`: the full-data projection — every exportable scalar column (long
162
+ * text included, binary excluded), and NO join/multi-join blocks: exports
163
+ * project scalar fields only, so fetching label/preview blocks for every row
164
+ * would be pure transfer waste.
165
+ */
166
+ fields?: 'grid' | 'export';
167
+ }
168
+ export declare function buildQuery(table: Table, schema: SchemaContextValue, filterString: string, columnFilters: ColumnFiltersState, id?: string, filterTable?: string, filterColumn?: string, options?: BuildQueryOptions): string | null;
52
169
  export interface DrillDownTarget {
53
170
  /** The parent table whose row owns the child collection. */
54
171
  parentTable: Table;
55
172
  /** The child collection field name on the parent type. */
56
173
  childField: string;
174
+ /** The parent multi-join that owns the child collection. Lets callers
175
+ * decide whether the drill can use a flat FK filter (simple single-column
176
+ * FK) or must keep the MODEL B parent traversal (composite/polymorphic). */
177
+ childJoin: Join;
57
178
  }
179
+ /**
180
+ * A drill can render as a flat root-filtered child grid — `childTable(filter:
181
+ * { fk: { _eq: $id } })`, i.e. a "parent grid with a filter applied" — only for
182
+ * a simple single-column, non-polymorphic FK. Composite FKs have no single
183
+ * filter column and polymorphic links need the server-injected discriminator;
184
+ * both must keep MODEL B's parent traversal.
185
+ */
186
+ export declare function canFlatFilterDrill(childJoin: Join): boolean;
58
187
  /**
59
188
  * Resolve the parent table + child collection field for a parent→child
60
- * related-records drill-down. The child is `table`; the parent is `tableFilter`
61
- * (or the destination of `table`'s single-join named `tableFilter` when the
189
+ * related-records drill-down. The child is `table`; the parent is `filterTable`
190
+ * (or the destination of `table`'s single-join named `filterTable` when the
62
191
  * relationship is described from the child side). `filterColumn` (the child
63
192
  * destination column) disambiguates when the parent has several multi-joins to
64
193
  * the same child. Returns `null` when no parent multi-join targets the child —
@@ -78,5 +207,5 @@ export interface PagedResult {
78
207
  * parent row was not found (no children / unknown id).
79
208
  */
80
209
  export declare function unwrapDrillDownPage(response: Record<string, unknown> | null | undefined, parentField: string, childField: string): PagedResult;
81
- export declare function resolveDrillDown(table: Table, schema: Schema, tableFilter?: string, filterColumn?: string): DrillDownTarget | null;
210
+ export declare function resolveDrillDown(table: Table, schema: SchemaContextValue, filterTable?: string, filterColumn?: string): DrillDownTarget | null;
82
211
  export {};
@@ -6,6 +6,7 @@ export interface PkEqFilterResult {
6
6
  params: string[];
7
7
  }
8
8
  type TableLike = Pick<Table, 'primaryKeys'> & Partial<Pick<Table, 'columns'>>;
9
+ export declare function decodePkPart(raw: string): string | null;
9
10
  /**
10
11
  * Encodes an ordered list of key values into a route string. The single source
11
12
  * of truth for composite-key route encoding — every producer (row lists) and
@@ -13,9 +14,28 @@ type TableLike = Pick<Table, 'primaryKeys'> & Partial<Pick<Table, 'columns'>>;
13
14
  * never drift apart and silently fail to match.
14
15
  */
15
16
  export declare function encodeRouteParts(values: readonly unknown[]): string;
17
+ /**
18
+ * Inverse of {@link encodeRouteParts}: splits a route-encoded id into its ordered
19
+ * values and percent-decodes each. `expectedCount <= 1` treats the whole string as a
20
+ * single value (never split, so a value that itself contains the delimiter survives).
21
+ * Throws when a multi-part id does not carry exactly `expectedCount` parts, so a caller
22
+ * building a composite filter cannot silently under-constrain it.
23
+ */
24
+ export declare function decodeRouteParts(raw: string, expectedCount: number): string[];
16
25
  export declare function rowIdOf(row: Record<string, unknown> | null | undefined, table: TableLike, index: number): string;
17
26
  export declare function pkFilterFor(row: Record<string, unknown> | null | undefined, table: TableLike): PkFilter | null;
18
27
  export declare function buildPkEqFilter(row: Record<string, unknown> | null | undefined, table: Pick<Table, 'primaryKeys' | 'columns'>): PkEqFilterResult | null;
19
28
  export declare function encodePkRoute(filter: PkFilter, table: TableLike): string;
29
+ /**
30
+ * Parses a route-encoded id back into a PK filter, or null when the route cannot
31
+ * key a row.
32
+ *
33
+ * An EMPTY segment is a parse failure, not a null key value. Primary-key columns
34
+ * are non-nullable, so `{line_id: null}` can never match a stored row — but it is
35
+ * a structurally valid filter, so callers guarding on "did the key resolve?"
36
+ * (see `pkResolveFailed` in data-edit.tsx) saw success, queried, matched zero
37
+ * rows, and then wrote an all-columns-blank UPDATE back. Returning null here is
38
+ * the established failure shape every caller already handles.
39
+ */
20
40
  export declare function parsePkRoute(raw: string, table: TableLike): PkFilter | null;
21
41
  export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,30 @@
1
+ /**
2
+ * One place that classifies a column's GraphQL scalar (`paramType`).
3
+ *
4
+ * The type mappers can emit Int, Short, Byte, BigInt, Decimal, Float, Boolean,
5
+ * DateTime, DateTimeOffset, JSON and String. Every surface that reasoned about
6
+ * types used to carry its own `['Int', 'Float']` list, so the four less common
7
+ * numeric scalars and the second timestamp scalar were each handled by whichever
8
+ * surfaces happened to remember them: a smallint column could not be filtered from
9
+ * the grid header, was not validated as a number in a form, and was sent as a
10
+ * string on write (which its `Short!` input rejects). Classifying in one module is
11
+ * what keeps those surfaces from drifting apart again.
12
+ */
13
+ /** Strips the non-null marker: `Int!` -> `Int`. */
14
+ export declare function baseParamType(paramType: string | undefined): string;
15
+ /** Whole-number columns — they reject a fractional value. */
16
+ export declare function isIntegerScalar(paramType: string | undefined): boolean;
17
+ /** Any numeric column, whole or fractional. */
18
+ export declare function isNumericScalar(paramType: string | undefined): boolean;
19
+ /** Columns whose value must never be routed through a JS number. */
20
+ export declare function isExactScalar(paramType: string | undefined): boolean;
21
+ /** DateTime and DateTimeOffset — the columns whose bounds are instants. */
22
+ export declare function isTimestampScalar(paramType: string | undefined): boolean;
23
+ export declare function isBooleanScalar(paramType: string | undefined): boolean;
24
+ export declare function isStringScalar(paramType: string | undefined): boolean;
25
+ /**
26
+ * Validates numeric text and returns the value to send: the digits unchanged for
27
+ * an exact column, otherwise a number. Null means "not (yet) a complete numeric
28
+ * literal", which callers read as "no value", never as zero.
29
+ */
30
+ export declare function parseScalarNumber(raw: string, paramType: string | undefined): number | string | null;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,24 @@
1
+ import { ComponentType } from 'react';
2
+ import { Table } from '../types/schema';
3
+ /**
4
+ * A host-contributed action on a TABLE in the navigation list. The editor renders
5
+ * every action in each table's kebab menu; what the action does is entirely the
6
+ * host's business (open a query designer, copy a connection string, …) — edit-db
7
+ * stays transport- and host-agnostic and just invokes the callback with the
8
+ * schema table. Built-in actions (Download CSV/JSON) use this same shape, so a
9
+ * host's contributions and the built-ins render as one uniform menu.
10
+ */
11
+ export interface TableAction {
12
+ /** Stable identity, e.g. `edit-as-query`. */
13
+ id: string;
14
+ /** Menu item label. */
15
+ label: string;
16
+ /** Optional 16px icon component (lucide-style: takes className). */
17
+ icon?: ComponentType<{
18
+ className?: string;
19
+ }>;
20
+ /** Hides the action for tables it does not apply to. Absent means always shown. */
21
+ enabled?: (table: Table) => boolean;
22
+ /** Invoked with the schema table when the user picks the action. */
23
+ onSelect: (table: Table) => void | Promise<void>;
24
+ }
@@ -0,0 +1,45 @@
1
+ import { Table } from '../types/schema';
2
+ export declare const TABLE_REF_LIMIT = 1000;
3
+ export declare const TABLE_REF_SEARCH_LIMIT = 50;
4
+ /**
5
+ * The column used as the display label for a table's FK dropdown rows.
6
+ * Falls back to schema-derived names only — never a guessed literal like `id`,
7
+ * which is a server error on tables without such a column: first primary-key
8
+ * column, then the first column. (Taking the first PK column here is a label
9
+ * fallback for display, not a composite-key shortcut — one column is all a
10
+ * label can show.) `||` (not `??`) so an empty-string labelColumn falls
11
+ * through instead of emitting `label: ` (a GraphQL syntax error).
12
+ */
13
+ export declare function refLabelColumn(table: Table): string;
14
+ export interface TableRefPlan {
15
+ query: string;
16
+ labelColumn: string;
17
+ /** True when the label column is String-typed and thus supports the
18
+ * server-side `_contains` search; callers fall back to client filtering. */
19
+ serverSearch: boolean;
20
+ }
21
+ /**
22
+ * Build the FK-dropdown list query. Sorted by the label column so the fetched
23
+ * window is deterministic. When `search` is non-empty and the label column is
24
+ * String-typed, the list is narrowed server-side (`_contains` on the label
25
+ * column) so parents beyond the fetch limit stay findable; otherwise the caller
26
+ * filters client-side over the fetched window.
27
+ */
28
+ export declare function tableRefPlan(table: Table, keyColumn: string, search?: string): TableRefPlan;
29
+ export interface TableRefLookupPlan {
30
+ query: string;
31
+ /** GraphQL type of the key column (non-null suffix stripped), for coercion. */
32
+ keyType: string;
33
+ }
34
+ /**
35
+ * Build the single-row lookup that resolves the label of the currently stored
36
+ * FK value when it falls outside the fetched dropdown window, so the Select can
37
+ * display it instead of a misleading placeholder.
38
+ */
39
+ export declare function tableRefLookupPlan(table: Table, keyColumn: string): TableRefLookupPlan;
40
+ /**
41
+ * Coerce a stored/route FK value to what the lookup's `$key` variable expects.
42
+ * Delegates to the shared coerceForGql so BigInt keys stay strings (Number()
43
+ * loses precision past 2^53) and Boolean keys stay booleans.
44
+ */
45
+ export declare function coerceKeyValue(keyType: string, value: unknown): unknown;
@@ -0,0 +1 @@
1
+ export {};
@@ -4,7 +4,7 @@
4
4
  * Scales to several hundred tables:
5
5
  * - Search filters by label, raw db name, and schema.
6
6
  * - Tables group under collapsible schema headers (only when >1 schema exists).
7
- * - The visible rows (headers + tables) are paged at {@link PAGE_SIZE} so the DOM
7
+ * - The visible rows (headers + tables) are paged at {@link TABLE_LIST_PAGE_SIZE} so the DOM
8
8
  * stays small regardless of total table count.
9
9
  *
10
10
  * @component
File without changes
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,22 @@
1
+ import { Table } from './types/schema';
2
+ import { DrillFrame } from './lib/drill-stack';
3
+ import { ColumnFiltersState } from '@tanstack/react-table';
4
+ interface TableViewParams {
5
+ table: Table;
6
+ id?: string;
7
+ filterTable?: string;
8
+ filterColumn?: string;
9
+ selectedRowId?: string | null;
10
+ onRowSelect?: (rowId: string | null) => void;
11
+ onOpenColumn?: (panel: DrillFrame) => void;
12
+ /** Current stacking (parent/child drill-down) mode state. */
13
+ stackingEnabled?: boolean;
14
+ /** Toggle stacking mode. When supplied, the grid renders the mode toggle. */
15
+ onToggleStacking?: (next: boolean) => void;
16
+ }
17
+ /** Converts the already schema-validated grid filter state into GraphQL input
18
+ * variables. No identifier text is accepted from the UI: columns are looked up
19
+ * in the current schema and operators are validated by the shared builder. */
20
+ export declare function chartFilter(filters: ColumnFiltersState, table: Table): Record<string, unknown> | undefined;
21
+ export declare function TableView({ table, id, filterTable, filterColumn, selectedRowId, onRowSelect, onOpenColumn, stackingEnabled, onToggleStacking }: TableViewParams): JSX.Element;
22
+ export {};
File without changes
@@ -57,16 +57,31 @@ export interface Column {
57
57
  isReadOnly: boolean;
58
58
  /** Custom metadata for the column */
59
59
  metadata: ColumnMetadata;
60
+ /**
61
+ * Dialect-decided LOB semantics from the server: true when the value may be
62
+ * arbitrarily large and should be fetched on demand. Authoritative over any
63
+ * dbType-name guess (Postgres text / SQLite TEXT are ordinary strings).
64
+ * Optional only for servers that predate the field.
65
+ */
66
+ isLargeValue?: boolean;
60
67
  /** Maximum string length (for validation) */
61
68
  maxLength?: number;
62
69
  /** Minimum string length (for validation) */
63
70
  minLength?: number;
64
- /** Minimum numeric value (for validation) */
65
- min?: number;
66
- /** Maximum numeric value (for validation) */
67
- max?: number;
71
+ /**
72
+ * Minimum bound (for validation). Numeric for number columns; an ISO date
73
+ * string for temporal columns — the server fills engine storable windows
74
+ * (SQL Server datetime >= 1753) when no metadata min is declared.
75
+ */
76
+ min?: number | string;
77
+ /** Maximum bound, same forms as min */
78
+ max?: number | string;
68
79
  /** Step increment for numeric inputs */
69
80
  step?: number;
81
+ /** Declared decimal precision from the database schema (null for non-decimal columns) */
82
+ precision?: number | null;
83
+ /** Declared decimal scale paired with precision */
84
+ scale?: number | null;
70
85
  /** Regex pattern for validation */
71
86
  pattern?: string;
72
87
  /** Error message for pattern validation failures */
@@ -75,10 +90,10 @@ export interface Column {
75
90
  inputType?: 'text' | 'email' | 'url' | 'tel' | 'date' | 'datetime-local' | 'number' | 'password' | 'search';
76
91
  /** Default value for new records */
77
92
  defaultValue?: string;
78
- /** Allowed enum values */
79
- enumValues?: string[];
80
- /** Display labels for enum values */
81
- enumLabels?: string[];
93
+ /** Allowed enum values. The server sends null, not undefined, for non-enum columns. */
94
+ enumValues?: string[] | null;
95
+ /** Display labels for enum values. Null for non-enum columns, as `enumValues` is. */
96
+ enumLabels?: string[] | null;
82
97
  }
83
98
  /**
84
99
  * Relationship join definition between tables.
@@ -95,6 +110,15 @@ export interface Join {
95
110
  destinationTable: string;
96
111
  /** Column names in the target table */
97
112
  destinationColumnNames: string[];
113
+ /** True for a polymorphic child link — the relation carries a server-side
114
+ * discriminator predicate (entity_type) rather than a plain FK. Such drills
115
+ * must keep the MODEL B parent traversal so the server injects the
116
+ * discriminator; a flat FK filter would leak sibling-type rows. */
117
+ isPolymorphic?: boolean;
118
+ /** Discriminator column backing a polymorphic link (when isPolymorphic). */
119
+ polymorphicTypeColumn?: string;
120
+ /** Discriminator value backing a polymorphic link (when isPolymorphic). */
121
+ polymorphicTypeValue?: string;
98
122
  }
99
123
  /**
100
124
  * Many-to-many bridge between this entity and a target entity through a
@@ -152,12 +176,32 @@ export interface Table {
152
176
  singleJoins: Join[];
153
177
  /** Many-to-many bridges through junction tables (optional; absent on older servers). */
154
178
  manyToManyJoins?: ManyToManyJoin[];
179
+ /** Database indexes (optional; absent on older servers). */
180
+ indexes?: TableIndex[];
181
+ }
182
+ /**
183
+ * A database index on a table, key columns in key order (GraphQL names).
184
+ * Lets the grid pick a default sort an index can serve — sorting a large
185
+ * table by an unindexed column re-sorts every row on every page.
186
+ * @interface TableIndex
187
+ */
188
+ export interface TableIndex {
189
+ /** Index name in the database catalog. */
190
+ name: string;
191
+ /** True when the index enforces uniqueness (includes PK indexes). */
192
+ isUnique: boolean;
193
+ /** True when the index defines the physical row order — the cheapest sort. */
194
+ isClustered: boolean;
195
+ /** True when the index backs the primary key constraint. */
196
+ isPrimaryKey: boolean;
197
+ /** Key column GraphQL names in key order; the leading column can be sorted for free. */
198
+ columns: string[];
155
199
  }
156
200
  /**
157
201
  * Schema context value containing database metadata and loading state.
158
- * @interface Schema
202
+ * @interface SchemaContextValue
159
203
  */
160
- export interface Schema {
204
+ export interface SchemaContextValue {
161
205
  /** Whether schema data is currently loading */
162
206
  loading: boolean;
163
207
  /** Error object if schema loading failed, null otherwise */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@standardbeagle/edit-db",
3
3
  "private": false,
4
- "version": "0.4.484",
4
+ "version": "0.4.581",
5
5
  "type": "module",
6
6
  "dependencies": {
7
7
  "@radix-ui/react-slot": "^1.2.4",
@@ -14,7 +14,6 @@
14
14
  "radix-ui": "^1.4.3",
15
15
  "react": "^18.3.1",
16
16
  "react-dom": "^18.3.1",
17
- "redux-actions": "^3.0.3",
18
17
  "tailwind-merge": "^3.6.0"
19
18
  },
20
19
  "devDependencies": {
@@ -27,7 +26,6 @@
27
26
  "@testing-library/react": "^16.3.2",
28
27
  "@types/react": "^18.3.29",
29
28
  "@types/react-dom": "^18.3.7",
30
- "@types/redux-actions": "^2.6.5",
31
29
  "@typescript-eslint/eslint-plugin": "^7.18.0",
32
30
  "@typescript-eslint/parser": "^7.18.0",
33
31
  "@vitejs/plugin-react-swc": "^4.3.1",
@@ -41,7 +39,7 @@
41
39
  "storybook": "^10.4.1",
42
40
  "tailwindcss": "^4.3.0",
43
41
  "typescript": "^5.9.3",
44
- "vite": "^7.3.5",
42
+ "vite": "^7.3.6",
45
43
  "vite-plugin-dts": "^4.5.4",
46
44
  "vitest": "^4.1.8"
47
45
  },
@@ -1,2 +0,0 @@
1
- export declare const GET_SCHEMA = "{\n schema: __schema {\n queryType {\n fields {\n name\n type {\n fields {\n name\n type {\n ofType {\n fields {\n name\n type {\n kind\n ofType {\n name\n }\n }\n }\n }\n }\n }\n }\n }\n }\n }\n }\n ";
2
- export declare const GET_DB_SCHEMA = "\nquery dbSchema {\n _dbSchema {\n dbName\n graphQlName\n labelColumn\n primaryKeys\n isEditable\n metadata {\n key\n value\n }\n columns {\n dbName\n graphQlName\n paramType\n dbType\n isPrimaryKey\n isIdentity\n isNullable\n isReadOnly\n maxLength\n minLength\n min\n max\n step\n pattern\n patternMessage\n inputType\n defaultValue\n enumValues\n enumLabels\n metadata {\n key\n value\n }\n }\n multiJoins {\n name\n fieldName\n sourceColumnNames\n destinationTable\n destinationColumnNames\n }\n singleJoins {\n name\n fieldName\n sourceColumnNames\n destinationTable\n destinationColumnNames\n }\n manyToManyJoins {\n name\n targetTable\n junctionTable\n junctionTargetField\n sourceColumnNames\n junctionSourceColumnNames\n junctionTargetColumnNames\n targetColumnNames\n hasPayload\n }\n }\n}";
@@ -1,17 +0,0 @@
1
- import { Table } from './types/schema';
2
- import { ColumnPanel } from './data-panel';
3
- interface DataDataTableParams {
4
- table: Table;
5
- id?: string;
6
- tableFilter?: string;
7
- filterColumn?: string;
8
- selectedRowId?: string | null;
9
- onRowSelect?: (rowId: string | null) => void;
10
- onOpenColumn?: (panel: ColumnPanel) => void;
11
- /** Current stacking (parent/child drill-down) mode state. */
12
- stackingEnabled?: boolean;
13
- /** Toggle stacking mode. When supplied, the grid renders the mode toggle. */
14
- onToggleStacking?: (next: boolean) => void;
15
- }
16
- export declare function DataDataTable({ table, id, tableFilter, filterColumn, selectedRowId, onRowSelect, onOpenColumn, stackingEnabled, onToggleStacking }: DataDataTableParams): JSX.Element;
17
- export {};
@@ -1 +0,0 @@
1
- export default function ErrorPage(): import("react").JSX.Element;