@standardbeagle/edit-db 0.4.494 → 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 (92) hide show
  1. package/dist/common/saved-objects.d.ts +40 -0
  2. package/dist/common/schema-queries.d.ts +1 -0
  3. package/dist/components/bulk-edit-dialog.d.ts +18 -0
  4. package/dist/components/content-viewer.d.ts +8 -1
  5. package/dist/components/data-table.d.ts +49 -2
  6. package/dist/components/detail-panel.d.ts +2 -2
  7. package/dist/components/export-button.d.ts +19 -0
  8. package/dist/components/export-button.test.d.ts +1 -0
  9. package/dist/components/filters/number-filter.d.ts +8 -1
  10. package/dist/components/fk-cell-popover.d.ts +5 -5
  11. package/dist/components/form-runner-host.d.ts +14 -0
  12. package/dist/components/form-runner.d.ts +43 -0
  13. package/dist/components/form-subform.d.ts +11 -0
  14. package/dist/components/form-subform.test.d.ts +0 -0
  15. package/dist/components/grouped-grid-summary.d.ts +17 -0
  16. package/dist/components/grouped-grid-summary.test.d.ts +0 -0
  17. package/dist/components/m2m-panel.d.ts +4 -5
  18. package/dist/components/ui/table.test.d.ts +1 -0
  19. package/dist/data-edit.d.ts +17 -4
  20. package/dist/data-panel.d.ts +0 -11
  21. package/dist/edit-db.css +1 -1
  22. package/dist/editor.d.ts +22 -1
  23. package/dist/editor.es.js +6822 -4695
  24. package/dist/editor.stories.d.ts +0 -1
  25. package/dist/editor.umd.cjs +41 -27
  26. package/dist/hooks/useColumnNav.d.ts +3 -3
  27. package/dist/hooks/useColumnSizingPersistence.d.ts +19 -0
  28. package/dist/hooks/useColumnVisibilityPersistence.d.ts +34 -0
  29. package/dist/hooks/useColumnVisibilityPersistence.test.d.ts +1 -0
  30. package/dist/hooks/useCompositeTableRef.d.ts +2 -2
  31. package/dist/hooks/useDataTable.d.ts +46 -5
  32. package/dist/hooks/useDebouncedCommit.test.d.ts +1 -0
  33. package/dist/hooks/useDeltaMutation.d.ts +20 -0
  34. package/dist/hooks/useDeltaMutation.test.d.ts +0 -0
  35. package/dist/hooks/useEditorConfig.d.ts +27 -0
  36. package/dist/hooks/useFitMode.d.ts +23 -0
  37. package/dist/hooks/useFitMode.test.d.ts +1 -0
  38. package/dist/hooks/usePanelSize.d.ts +28 -0
  39. package/dist/hooks/usePanelSize.test.d.ts +1 -0
  40. package/dist/hooks/usePath.d.ts +1 -0
  41. package/dist/hooks/useRowHoverActions.d.ts +26 -0
  42. package/dist/hooks/useSchema.d.ts +8 -2
  43. package/dist/hooks/useSchemaFilter.test.d.ts +1 -0
  44. package/dist/hooks/useTableExport.d.ts +13 -0
  45. package/dist/hooks/useTableMutation.d.ts +1 -4
  46. package/dist/hooks/useTableRef.d.ts +3 -3
  47. package/dist/hooks/useTableStats.d.ts +0 -24
  48. package/dist/index.d.ts +13 -1
  49. package/dist/lib/column-order.d.ts +17 -0
  50. package/dist/lib/column-order.test.d.ts +1 -0
  51. package/dist/lib/content-detect.d.ts +34 -0
  52. package/dist/lib/content-detect.sniff.test.d.ts +1 -0
  53. package/dist/lib/drill-stack.d.ts +19 -2
  54. package/dist/lib/export.d.ts +122 -0
  55. package/dist/lib/export.test.d.ts +1 -0
  56. package/dist/lib/field-validation.d.ts +16 -0
  57. package/dist/lib/fk.d.ts +13 -0
  58. package/dist/lib/form-definition.d.ts +57 -0
  59. package/dist/lib/form-definition.test.d.ts +1 -0
  60. package/dist/lib/form-runner-nav.d.ts +19 -0
  61. package/dist/lib/form-runner-nav.test.d.ts +1 -0
  62. package/dist/lib/form-subform.d.ts +12 -0
  63. package/dist/lib/form-subform.test.d.ts +1 -0
  64. package/dist/lib/form-widget.d.ts +36 -0
  65. package/dist/lib/form-widget.test.d.ts +1 -0
  66. package/dist/lib/format-value.d.ts +26 -0
  67. package/dist/lib/grid-grouping.d.ts +63 -0
  68. package/dist/lib/grid-grouping.test.d.ts +1 -0
  69. package/dist/lib/m2m.d.ts +26 -1
  70. package/dist/lib/mutation-payload.d.ts +37 -0
  71. package/dist/lib/mutation-payload.test.d.ts +1 -0
  72. package/dist/lib/query-builder.d.ts +116 -9
  73. package/dist/lib/row-id.d.ts +11 -0
  74. package/dist/lib/rows-by-pk-query.test.d.ts +1 -0
  75. package/dist/lib/scalar-types.d.ts +30 -0
  76. package/dist/lib/scalar-types.test.d.ts +1 -0
  77. package/dist/lib/table-action.d.ts +24 -0
  78. package/dist/{tableList.d.ts → table-list.d.ts} +1 -1
  79. package/dist/table-list.test.d.ts +0 -0
  80. package/dist/table-view.chart.test.d.ts +1 -0
  81. package/dist/table-view.d.ts +22 -0
  82. package/dist/table-view.test.d.ts +0 -0
  83. package/dist/types/schema.d.ts +45 -10
  84. package/package.json +2 -2
  85. package/dist/common/schema.d.ts +0 -2
  86. package/dist/data-data-table.d.ts +0 -17
  87. /package/dist/common/{schema.test.d.ts → saved-objects.test.d.ts} +0 -0
  88. /package/dist/{components/filters/use-debounced-commit.test.d.ts → common/schema-queries.test.d.ts} +0 -0
  89. /package/dist/{data-data-table.test.d.ts → components/bulk-edit-dialog.test.d.ts} +0 -0
  90. /package/dist/components/{table-dimensions.test.d.ts → content-viewer.test.d.ts} +0 -0
  91. /package/dist/{tableList.test.d.ts → components/form-runner.test.d.ts} +0 -0
  92. /package/dist/{components/filters/use-debounced-commit.d.ts → hooks/useDebouncedCommit.d.ts} +0 -0
@@ -0,0 +1,122 @@
1
+ /**
2
+ * The single, framework-free result-export utility for the shipped edit-db
3
+ * chain. Every export surface — the data grid, the raw SQL console result grid,
4
+ * and any future report runner — serializes through THIS module so there is
5
+ * exactly one CSV/JSON implementation in the shipped path.
6
+ *
7
+ * The serializers are matrix-based (`string[]` headers + `unknown[][]` rows) so
8
+ * they serve both the grid (record rows projected onto ordered fields) and the
9
+ * console (already-columnar positional rows) without a second code path.
10
+ *
11
+ * Value coercion is deliberate about the type classes that have bitten edit-db
12
+ * before:
13
+ * - NULL vs empty string are distinguishable in CSV (NULL -> empty field,
14
+ * "" -> a quoted empty pair, the PostgreSQL CSV convention).
15
+ * - bigint is rendered as its exact decimal string, never coerced through
16
+ * Number (which silently rounds past 2^53 — a known BigInt-PK bug class).
17
+ * - Date is rendered as a stable ISO-8601 string.
18
+ */
19
+ export type ExportFormat = 'csv' | 'json';
20
+ export type JsonMode = 'array' | 'lines';
21
+ /** The UTF-8 byte-order mark. Prepending it makes Excel open the CSV as UTF-8. */
22
+ export declare const UTF8_BOM = "\uFEFF";
23
+ /**
24
+ * Render one cell value as an RFC4180 CSV field.
25
+ *
26
+ * `null`/`undefined` become a bare empty field; an empty string becomes a
27
+ * quoted empty pair (`""`) so the two stay distinguishable in the output bytes.
28
+ * bigint and Date get exact/stable string forms; objects are JSON-encoded.
29
+ */
30
+ export declare function formatCsvCell(value: unknown): string;
31
+ export interface CsvOptions {
32
+ /** Prepend the UTF-8 BOM so Excel detects the encoding. Default false. */
33
+ bom?: boolean;
34
+ }
35
+ /**
36
+ * Serialize a header row plus data rows into an RFC4180 CSV string. Rows are
37
+ * joined with CRLF as the RFC specifies. `rows` are positional, aligned to
38
+ * `headers`.
39
+ */
40
+ export declare function buildCsv(headers: readonly string[], rows: ReadonlyArray<readonly unknown[]>, options?: CsvOptions): string;
41
+ export interface JsonOptions {
42
+ /** `array` (default): a single pretty-printed JSON array. `lines`: NDJSON. */
43
+ mode?: JsonMode;
44
+ }
45
+ /**
46
+ * Serialize header + rows into JSON, keyed by the header names. `array` mode
47
+ * emits one pretty-printed array; `lines` mode emits newline-delimited JSON
48
+ * (one compact object per line). bigint PKs round-trip losslessly as strings.
49
+ */
50
+ export declare function buildJson(headers: readonly string[], rows: ReadonlyArray<readonly unknown[]>, options?: JsonOptions): string;
51
+ /** One page of the source result set, as returned by the paging fetcher. */
52
+ export interface ExportPage {
53
+ /** Positional rows, aligned to the export headers. */
54
+ rows: unknown[][];
55
+ /** The server's total row count for the current filter (drives the loop). */
56
+ total: number;
57
+ }
58
+ export interface ExportAllOptions {
59
+ headers: string[];
60
+ format: ExportFormat;
61
+ /**
62
+ * Pull one page at the given offset. This is the ONLY data seam — the caller
63
+ * closes over the existing fetcher/query-builder so the current filters and
64
+ * sort are honored, and no new HTTP client is introduced.
65
+ */
66
+ fetchPage: (offset: number, limit: number) => Promise<ExportPage>;
67
+ /** Page size for each fetch. Default 500. */
68
+ pageSize?: number;
69
+ /** Hard stop after this many rows; the result is marked `truncated`. */
70
+ rowCap?: number;
71
+ /** Abort mid-export; the promise rejects and no content is assembled. */
72
+ signal?: AbortSignal;
73
+ /** Called after each page with the running fetched count and the total. */
74
+ onProgress?: (fetched: number, total: number) => void;
75
+ csv?: CsvOptions;
76
+ json?: JsonOptions;
77
+ }
78
+ export interface ExportResult {
79
+ content: string;
80
+ rowCount: number;
81
+ total: number;
82
+ truncated: boolean;
83
+ }
84
+ /**
85
+ * Drain the full result set page by page and serialize it once at the end.
86
+ *
87
+ * Cancellation is fail-safe: an aborted signal makes this throw before any
88
+ * content string is built, so the caller — which only writes a file on a
89
+ * resolved result — never produces a partial file. The row cap stops paging
90
+ * early and flags `truncated` so the caller can warn.
91
+ */
92
+ export declare function exportAllRows(options: ExportAllOptions): Promise<ExportResult>;
93
+ /**
94
+ * Default confirmation threshold: exporting more than this many rows prompts the
95
+ * user first, since it means paging the full result set out of the database.
96
+ */
97
+ export declare const DEFAULT_ROW_CAP = 100000;
98
+ /** Options passed to a bound {@link ExportRunner}. */
99
+ export interface RunExportOptions {
100
+ format: ExportFormat;
101
+ signal?: AbortSignal;
102
+ onProgress?: (fetched: number, total: number) => void;
103
+ rowCap?: number;
104
+ }
105
+ /**
106
+ * A surface-bound export runner: closes over the surface's headers + paging
107
+ * fetcher and drains the full result set. Returns null when the surface has
108
+ * nothing to export (no active query/table). This is the seam the grid toolbar
109
+ * and any other paged surface hand to the shared export button.
110
+ */
111
+ export type ExportRunner = (options: RunExportOptions) => Promise<ExportResult | null>;
112
+ /** MIME type for a downloaded file of the given format. */
113
+ export declare function mimeFor(format: ExportFormat): string;
114
+ /** `<base>-export.<ext>` filename for the given format. */
115
+ export declare function filenameFor(base: string, format: ExportFormat): string;
116
+ /**
117
+ * Trigger a browser download of `content` as `filename`. Builds a Blob, clicks a
118
+ * transient `<a download>`, then revokes the object URL. No-ops outside a DOM.
119
+ */
120
+ export declare function downloadTextFile(content: string, filename: string, mimeType: string): void;
121
+ /** Download a base64-encoded binary value as a file (image, PDF, any blob). */
122
+ export declare function downloadBinaryFile(base64: string, filename: string, mimeType: string): void;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,4 +1,10 @@
1
1
  import { Column } from '../types/schema';
2
+ /**
3
+ * The temporal family of a column, from its DATABASE type (mirrors the server's
4
+ * ValidationRules.TemporalKindOf) — paramType can't carry this, because
5
+ * temporal mutation inputs are String on the wire.
6
+ */
7
+ export declare function temporalKindOf(column: Column): 'datetime' | 'date' | 'time' | undefined;
2
8
  /**
3
9
  * Anchors a pattern the same way the HTML5 `pattern` attribute and the server
4
10
  * validator do: wrap as ^(?:...)$ unless the author already anchored it with ^.
@@ -10,3 +16,13 @@ export declare function anchorPattern(pattern: string): string;
10
16
  * callers keep their own required policy (forms use !isNullable).
11
17
  */
12
18
  export declare function validateFieldValue(column: Column, value: unknown, isRequired: boolean): string | undefined;
19
+ /**
20
+ * Validates every value PRESENT in a mutation payload against its column's
21
+ * schema rules — the pre-send gate the mutation hooks run so EVERY write path
22
+ * (forms, programmatic callers, future surfaces) is checked, not only fields a
23
+ * form happened to attach validators to. Absent/cleared values are skipped:
24
+ * required-ness is form policy (a NOT NULL column with a DB default is legal to
25
+ * omit on insert), and this gate must never refuse what the server accepts.
26
+ * Returns all failures, or an empty array when the payload is clean.
27
+ */
28
+ export declare function validateRowValues(columns: readonly Column[], detail: Record<string, unknown>): string[];
package/dist/lib/fk.d.ts CHANGED
@@ -15,6 +15,19 @@ export declare function coerceForGql(value: unknown, gqlType: string): unknown;
15
15
  export declare function gqlTypeOf(column: Column | undefined): string;
16
16
  export declare function isFkMember(join: Join, columnName: string): boolean;
17
17
  export declare function isComposite(join: Join): boolean;
18
+ /**
19
+ * The destination column paired POSITIONALLY with `columnName` on the source side
20
+ * of `join`, or null when the pairing does not exist.
21
+ *
22
+ * Null is the whole point. The previous inline form was
23
+ * `destinationColumnNames[indexOf(name)] ?? destinationColumnNames[0]`: `indexOf`
24
+ * returns -1 for a column that is not part of the join, the indexed read is
25
+ * `undefined`, and `??` then substituted the FIRST destination column — so an
26
+ * unresolvable pairing silently produced a confident, wrong FK target instead of
27
+ * an absent one. Callers must handle null by declining the FK behaviour, never by
28
+ * guessing a column (see .claude/rules/composite-pk-compliance.md).
29
+ */
30
+ export declare function fkDestinationColumnFor(join: Join, columnName: string): string | null;
18
31
  export declare function findJoinBySource(joins: Join[], columnName: string): Join | undefined;
19
32
  /**
20
33
  * Build a `{_eq}`-per-column composite filter against the destination side of a join,
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Saved-form definition shape consumed by the form runner.
3
+ *
4
+ * A {@link FormDefinition} is the builder's output (see the desktop shell's
5
+ * `forms/form-state.ts`): a bound single-record data-entry form over one table.
6
+ * The runner binds it to live data. The types are re-declared here — rather than
7
+ * imported from the shell — to keep this shipped stack self-contained (the same
8
+ * choice `common/saved-objects.ts` makes for its `SavedObject` contract), and to
9
+ * give the runner a tolerant parser for the `unknown` `SavedObject.definition`
10
+ * payload it is handed.
11
+ */
12
+ /** The data-entry widget a field renders as. Mirrors the builder's FormControlType. */
13
+ export type FormControlType = 'text' | 'textarea' | 'number' | 'checkbox' | 'date' | 'datetime' | 'select';
14
+ export interface FormField {
15
+ /** Column name within the bound table. */
16
+ column: string;
17
+ /** Display label shown beside the control. */
18
+ label: string;
19
+ control: FormControlType;
20
+ /** Read-only controls render disabled. */
21
+ readOnly: boolean;
22
+ /** Required marker (runtime enforces via field validation). */
23
+ required: boolean;
24
+ /** Whether the field appears on the form. */
25
+ include: boolean;
26
+ }
27
+ /** A schema-derived related-record section below the parent form. */
28
+ export interface FormSubform {
29
+ /** Stable relationship id, normally the parent collection field. */
30
+ relationship: string;
31
+ childTable: string;
32
+ /** Parent and child FK columns are parallel; composite links include every pair. */
33
+ parentColumns: string[];
34
+ childColumns: string[];
35
+ label: string;
36
+ mode: 'grid' | 'stacked';
37
+ }
38
+ export interface FormDefinition {
39
+ /** Identifier of the bound table (qualified "schema.name" or GraphQL name). */
40
+ table: string;
41
+ /** Form caption. */
42
+ title: string;
43
+ /** Layout columns (1–4): how many fields sit side-by-side. */
44
+ columns: number;
45
+ fields: FormField[];
46
+ subforms?: FormSubform[];
47
+ }
48
+ /**
49
+ * Validates an unknown value (e.g. a `SavedObject.definition`) as a
50
+ * {@link FormDefinition}. Returns null when it cannot be bound — a missing table,
51
+ * or no parseable fields, means there is nothing to render. Unparseable
52
+ * individual fields are dropped (tolerant), mirroring the builder's storage
53
+ * parser, so one bad field never blanks the whole form.
54
+ */
55
+ export declare function parseFormDefinition(value: unknown): FormDefinition | null;
56
+ /** The included fields, in definition order — what the runner renders. */
57
+ export declare function visibleFields(def: FormDefinition): FormField[];
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Pure record-cursor math for the form runner's first/prev/next/last navigation.
3
+ *
4
+ * The runner browses a table one record at a time by absolute offset (a
5
+ * `limit: 1 offset: <index>` fetch), so the only stateful arithmetic is moving a
6
+ * bounded index across `[0, total)`. Keeping it here makes it unit-testable
7
+ * without a fetcher or DOM, and keeps the component thin.
8
+ */
9
+ export type NavDirection = 'first' | 'prev' | 'next' | 'last';
10
+ /**
11
+ * Resolves the target absolute index for a navigation step, clamped into the
12
+ * valid range. `total <= 0` (empty table) always resolves to 0 so callers show
13
+ * the "no records" state rather than a negative offset.
14
+ */
15
+ export declare function nextIndex(current: number, total: number, direction: NavDirection): number;
16
+ /** Whether a navigation step from `current` would actually move (false at a bound). */
17
+ export declare function canNavigate(current: number, total: number, direction: NavDirection): boolean;
18
+ /** Human 1-based position label, e.g. "3 of 12". Empty table reads "0 of 0". */
19
+ export declare function positionLabel(index: number, total: number): string;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,12 @@
1
+ import { Join, Table } from '../types/schema';
2
+ export interface ChildFilterPlan {
3
+ params: string[];
4
+ filter: string;
5
+ variables: Record<string, unknown>;
6
+ }
7
+ /** True only when every parent-key/FK pair can be safely bound. */
8
+ export declare function relationshipIsUsable(parent: Table, child: Table, join: Join): boolean;
9
+ /** Builds a parameterized child filter; composite relationships bind every pair. */
10
+ export declare function childFilterPlan(parent: Table, child: Table, join: Join, row: Record<string, unknown> | null): ChildFilterPlan | null;
11
+ /** Values supplied to a child create form; callers hide these FK controls. */
12
+ export declare function childPrefill(join: Join, row: Record<string, unknown> | null): Record<string, unknown>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,36 @@
1
+ import { FormControlType, FormField } from './form-definition';
2
+ /** Structural subset of the app-metadata `FieldMetadata` this module reads. */
3
+ export interface FieldWidgetHint {
4
+ /** Widget hint (e.g. `text`, `select`) — overrides the saved control when recognised. */
5
+ widget?: string;
6
+ /** Whether the field is visible. Metadata default is `true`. */
7
+ visible?: boolean;
8
+ /** Whether the field is read-only. Metadata default is `false`. */
9
+ readOnly?: boolean;
10
+ }
11
+ /** The rendered control plus its resolved read-only / visible state. */
12
+ export interface ResolvedWidget {
13
+ control: FormControlType;
14
+ readOnly: boolean;
15
+ visible: boolean;
16
+ }
17
+ /**
18
+ * Resolves the effective widget for a field. The metadata widget hint (when it
19
+ * names a known control) wins over the saved control; read-only is the union of
20
+ * both sources (either can lock a field, neither can unlock what the other
21
+ * locked); visibility requires the field be included AND not hidden by metadata.
22
+ */
23
+ export declare function resolveWidget(field: FormField, meta?: FieldWidgetHint): ResolvedWidget;
24
+ /** How a control maps onto a DOM element for rendering. */
25
+ export type ControlRender = {
26
+ kind: 'input';
27
+ type: string;
28
+ } | {
29
+ kind: 'textarea';
30
+ } | {
31
+ kind: 'checkbox';
32
+ } | {
33
+ kind: 'select';
34
+ };
35
+ /** Maps a control type to the element the runner should render. */
36
+ export declare function controlRender(control: FormControlType): ControlRender;
@@ -0,0 +1 @@
1
+ export {};
@@ -51,3 +51,29 @@ export declare function formatRelative(date: Date, now?: Date): string;
51
51
  * `title` revealing the exact value), or the plain string otherwise.
52
52
  */
53
53
  export declare function formatColumnValue(value: unknown, column: Column, options?: FormatOptions): ReactNode;
54
+ /**
55
+ * Format a raw date/datetime string for grid-cell display via toLocaleString,
56
+ * blanking sentinel "unset" instants.
57
+ *
58
+ * Only exact placeholder/sentinel instants are blanked, not legitimate
59
+ * historical dates. A broad "before 1973" cutoff hid real dates (e.g. a 1900
60
+ * birthdate or a 1969 record). The two markers that actually mean "unset":
61
+ * the Unix epoch (timestamp 0, i.e. 1970-01-01T00:00:00Z) and .NET/SQL
62
+ * DateTime.MinValue (year 0001-01-01).
63
+ */
64
+ export declare function formatDateCellValue(value: string): string;
65
+ /**
66
+ * Formats a number into an abbreviated string representation.
67
+ *
68
+ * @example
69
+ * ```typescript
70
+ * abbreviateNumber(12) // "12"
71
+ * abbreviateNumber(1200) // "1.2k"
72
+ * abbreviateNumber(45000) // "45k"
73
+ * abbreviateNumber(1200000) // "1.2M"
74
+ * ```
75
+ *
76
+ * @param num - Number to abbreviate
77
+ * @returns Abbreviated string representation
78
+ */
79
+ export declare function abbreviateNumber(num: number | null): string;
@@ -0,0 +1,63 @@
1
+ import { ColumnFiltersState } from '@tanstack/react-table';
2
+ import { Column, Table } from '../types/schema';
3
+ /** URL parameter that owns the selected grouping field. */
4
+ export declare const GRID_GROUP_BY_PARAM = "gb";
5
+ /** Optional, URL-owned numeric measure shown beside every server aggregate. */
6
+ export declare const GRID_GROUP_SUM_PARAM = "gs";
7
+ /**
8
+ * Resolves the URL value against the current schema. Keeping this lookup at
9
+ * the boundary means a stale browser-history value can never become GraphQL
10
+ * source text for a different table.
11
+ */
12
+ export declare function groupingColumnFromUrl(value: string | null, table: Table | null): Column | null;
13
+ /** The aggregate result is deliberately server-owned; it is never calculated from page rows. */
14
+ export interface GroupingRow {
15
+ value: unknown;
16
+ count: number;
17
+ sum: unknown;
18
+ }
19
+ /**
20
+ * Grouped results have their own aggregate-row ordering. It is intentionally
21
+ * separate from the flat-grid sort: the latter orders individual records,
22
+ * while this controls aggregate buckets. Count ties always use the group key
23
+ * ascending so pagination remains deterministic.
24
+ */
25
+ export type GroupingSort = {
26
+ field: 'key' | 'count';
27
+ desc: boolean;
28
+ };
29
+ export declare const DEFAULT_GROUPING_SORT: GroupingSort;
30
+ export interface GridGroupingRequest {
31
+ query: string;
32
+ variables: Record<string, unknown>;
33
+ groupBy: Column;
34
+ sumBy: Column | null;
35
+ sort: GroupingSort;
36
+ }
37
+ export interface GridGroupMemberRequest {
38
+ query: string;
39
+ variables: Record<string, unknown>;
40
+ /** Response field selected from the current schema, never parsed from query text. */
41
+ responseKey: string;
42
+ }
43
+ export declare function groupingSumColumnFromUrl(value: string | null, table: Table | null): Column | null;
44
+ /**
45
+ * Update one of the grouping-owned URL fields without disturbing filters, the
46
+ * selected profile, or other application-owned state. The caller receives a
47
+ * fresh instance so it is safe to use during render/effects as well as event
48
+ * handlers.
49
+ */
50
+ export declare function withGroupingUrlParam(search: URLSearchParams, param: typeof GRID_GROUP_BY_PARAM | typeof GRID_GROUP_SUM_PARAM, value: string | null): URLSearchParams;
51
+ /** Remove grouping state when navigation changes the source table. */
52
+ export declare function withoutGroupingUrlParams(search: URLSearchParams): URLSearchParams;
53
+ /** Builds the schema-derived aggregate document used by the grouped grid. */
54
+ export declare function buildGridGroupingRequest(table: Table, groupBy: Column, columnFilters: ColumnFiltersState, headerFilter: string, sumBy?: Column | null, sort?: GroupingSort): GridGroupingRequest;
55
+ export declare function readGroupingRows(data: Record<string, unknown> | undefined, table: Table, groupBy: Column, sort?: GroupingSort): GroupingRow[];
56
+ /** Defined aggregate ordering for the server-grouped replacement surface. */
57
+ export declare function compareGroupingRowsByKey(left: GroupingRow, right: GroupingRow): number;
58
+ /** Sort aggregate buckets by the selected key/count direction with a stable key tie-breaker. */
59
+ export declare function compareGroupingRows(left: GroupingRow, right: GroupingRow, sort?: GroupingSort): number;
60
+ /** Reads a selected configured measure without ever deriving it from page rows. */
61
+ export declare function readGroupingRowsWithSum(data: Record<string, unknown> | undefined, table: Table, groupBy: Column, sumBy: Column | null, sort?: GroupingSort): GroupingRow[];
62
+ /** Schema-derived member query for a single aggregate key. It merges, never replaces, active filters. */
63
+ export declare function buildGridGroupMemberRequest(table: Table, groupBy: Column, value: unknown, columnFilters: ColumnFiltersState, headerFilter: string, sort?: readonly string[]): GridGroupMemberRequest;
@@ -0,0 +1 @@
1
+ export {};
package/dist/lib/m2m.d.ts CHANGED
@@ -31,11 +31,36 @@ export declare function m2mRowsQuery(junction: Table, target: Table, m2m: ManyTo
31
31
  };
32
32
  export interface M2mTargetPickerPlan {
33
33
  query: string;
34
- idColumn: string;
34
+ /**
35
+ * Columns that together identify a target row — every PK column in declaration
36
+ * order. Both the picker and the already-linked set must key on ALL of them
37
+ * (via {@link m2mTargetIdentityColumns}) or two rows sharing their first key
38
+ * column collapse into one.
39
+ */
40
+ identityColumns: string[];
41
+ /**
42
+ * The target column the junction FK references — the value written on attach.
43
+ * Deliberately separate from identity: on a composite-PK target, or a junction
44
+ * that points at a unique non-PK column, the two are different columns.
45
+ */
46
+ linkColumn: string;
35
47
  /** True when the label column is String-typed and thus supports the
36
48
  * server-side `_contains` search; callers fall back to client filtering. */
37
49
  serverSearch: boolean;
38
50
  }
51
+ /**
52
+ * The columns that identify a target row, in declaration order.
53
+ *
54
+ * Single source of truth for BOTH sides of the picker's duplicate check — the
55
+ * fetched target rows and the already-linked rows read off the junction. Keying
56
+ * the two independently (or on the first PK column alone) let two distinct composite-PK
57
+ * rows that share their first key column collapse: one was wrongly marked
58
+ * "Already linked" and its button disabled, making a real target unpickable.
59
+ *
60
+ * Falls back to the junction's target columns when the target declares no primary
61
+ * key, which is the only identity available in that case.
62
+ */
63
+ export declare function m2mTargetIdentityColumns(target: Table, m2m: ManyToManyJoin): string[];
39
64
  /**
40
65
  * Build the target-list query used when adding a many-to-many link.
41
66
  * Sort values are BifrostQL enum literals, so they must not be quoted.
@@ -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 {};