@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.
- package/dist/common/fetcher.d.ts +7 -2
- package/dist/common/saved-objects.d.ts +40 -0
- package/dist/common/schema-queries.d.ts +1 -0
- package/dist/components/bulk-edit-dialog.d.ts +18 -0
- package/dist/components/content-panel.d.ts +5 -2
- package/dist/components/content-viewer.d.ts +8 -1
- package/dist/components/content-viewer.test.d.ts +1 -0
- package/dist/components/data-table.d.ts +49 -2
- package/dist/components/detail-panel.d.ts +2 -2
- package/dist/components/export-button.d.ts +19 -0
- package/dist/components/export-button.test.d.ts +1 -0
- package/dist/components/filters/fk-filter.d.ts +2 -0
- package/dist/components/filters/fk-filter.test.d.ts +1 -0
- package/dist/components/filters/number-filter.d.ts +8 -0
- package/dist/components/filters/number-filter.test.d.ts +1 -0
- package/dist/components/fk-cell-popover.d.ts +12 -5
- package/dist/components/fk-cell-popover.test.d.ts +1 -0
- package/dist/components/form-runner-host.d.ts +14 -0
- package/dist/components/form-runner.d.ts +43 -0
- package/dist/components/form-runner.test.d.ts +0 -0
- package/dist/components/form-subform.d.ts +11 -0
- package/dist/components/form-subform.test.d.ts +0 -0
- package/dist/components/grouped-grid-summary.d.ts +17 -0
- package/dist/components/grouped-grid-summary.test.d.ts +0 -0
- package/dist/components/m2m-panel.d.ts +4 -5
- package/dist/components/ui/table.test.d.ts +1 -0
- package/dist/data-edit.d.ts +25 -4
- package/dist/data-panel.d.ts +0 -11
- package/dist/edit-db.css +1 -1
- package/dist/editor.d.ts +22 -1
- package/dist/editor.es.js +7368 -4589
- package/dist/editor.stories.d.ts +0 -1
- package/dist/editor.umd.cjs +48 -27
- package/dist/hooks/useColumnNav.d.ts +3 -3
- package/dist/hooks/useColumnSizingPersistence.d.ts +19 -0
- package/dist/hooks/useColumnVisibilityPersistence.d.ts +34 -0
- package/dist/hooks/useColumnVisibilityPersistence.test.d.ts +1 -0
- package/dist/hooks/useCompositeTableRef.d.ts +20 -2
- package/dist/hooks/useCompositeTableRef.test.d.ts +1 -0
- package/dist/hooks/useDataTable.d.ts +59 -6
- package/dist/hooks/useDebouncedCommit.d.ts +19 -0
- package/dist/hooks/useDebouncedCommit.test.d.ts +1 -0
- package/dist/hooks/useDeltaMutation.d.ts +20 -0
- package/dist/hooks/useDeltaMutation.test.d.ts +0 -0
- package/dist/hooks/useEditorConfig.d.ts +27 -0
- package/dist/hooks/useFitMode.d.ts +23 -0
- package/dist/hooks/useFitMode.test.d.ts +1 -0
- package/dist/hooks/usePanelSize.d.ts +28 -0
- package/dist/hooks/usePanelSize.test.d.ts +1 -0
- package/dist/hooks/usePath.d.ts +2 -5
- package/dist/hooks/useRowHoverActions.d.ts +26 -0
- package/dist/hooks/useSchema.d.ts +8 -2
- package/dist/hooks/useSchemaFilter.test.d.ts +1 -0
- package/dist/hooks/useTableExport.d.ts +13 -0
- package/dist/hooks/useTableMutation.d.ts +1 -4
- package/dist/hooks/useTableMutation.test.d.ts +0 -0
- package/dist/hooks/useTableRef.d.ts +23 -2
- package/dist/hooks/useTableRef.test.d.ts +1 -0
- package/dist/hooks/useTableStats.d.ts +7 -24
- package/dist/hooks/useToast.d.ts +15 -0
- package/dist/index.d.ts +13 -1
- package/dist/lib/column-order.d.ts +17 -0
- package/dist/lib/column-order.test.d.ts +1 -0
- package/dist/lib/content-detect.d.ts +39 -0
- package/dist/lib/content-detect.sniff.test.d.ts +1 -0
- package/dist/lib/date-input.d.ts +21 -0
- package/dist/lib/date-input.test.d.ts +1 -0
- package/dist/lib/drill-stack.d.ts +19 -2
- package/dist/lib/export.d.ts +122 -0
- package/dist/lib/export.test.d.ts +1 -0
- package/dist/lib/field-validation.d.ts +16 -0
- package/dist/lib/fk.d.ts +23 -2
- package/dist/lib/form-definition.d.ts +57 -0
- package/dist/lib/form-definition.test.d.ts +1 -0
- package/dist/lib/form-runner-nav.d.ts +19 -0
- package/dist/lib/form-runner-nav.test.d.ts +1 -0
- package/dist/lib/form-subform.d.ts +12 -0
- package/dist/lib/form-subform.test.d.ts +1 -0
- package/dist/lib/form-widget.d.ts +36 -0
- package/dist/lib/form-widget.test.d.ts +1 -0
- package/dist/lib/format-value.d.ts +26 -0
- package/dist/lib/grid-grouping.d.ts +63 -0
- package/dist/lib/grid-grouping.test.d.ts +1 -0
- package/dist/lib/invalidate.d.ts +7 -0
- package/dist/lib/invalidate.test.d.ts +1 -0
- package/dist/lib/label-match.d.ts +13 -0
- package/dist/lib/label-match.test.d.ts +1 -0
- package/dist/lib/m2m.d.ts +45 -6
- package/dist/lib/mutation-payload.d.ts +37 -0
- package/dist/lib/mutation-payload.test.d.ts +1 -0
- package/dist/lib/query-builder.d.ts +137 -8
- package/dist/lib/row-id.d.ts +20 -0
- package/dist/lib/rows-by-pk-query.test.d.ts +1 -0
- package/dist/lib/scalar-types.d.ts +30 -0
- package/dist/lib/scalar-types.test.d.ts +1 -0
- package/dist/lib/table-action.d.ts +24 -0
- package/dist/lib/table-ref.d.ts +45 -0
- package/dist/lib/table-ref.test.d.ts +1 -0
- package/dist/{tableList.d.ts → table-list.d.ts} +1 -1
- package/dist/table-list.test.d.ts +0 -0
- package/dist/table-view.chart.test.d.ts +1 -0
- package/dist/table-view.d.ts +22 -0
- package/dist/table-view.test.d.ts +0 -0
- package/dist/types/schema.d.ts +54 -10
- package/package.json +2 -4
- package/dist/common/schema.d.ts +0 -2
- package/dist/data-data-table.d.ts +0 -17
- package/dist/error-panel.d.ts +0 -1
- /package/dist/common/{schema.test.d.ts → saved-objects.test.d.ts} +0 -0
- /package/dist/{components/table-dimensions.test.d.ts → common/schema-queries.test.d.ts} +0 -0
- /package/dist/{tableList.test.d.ts → components/bulk-edit-dialog.test.d.ts} +0 -0
|
@@ -4,8 +4,25 @@ export type ContentKind = 'json' | 'xml' | 'php-serialized' | 'binary' | 'longte
|
|
|
4
4
|
export declare function isBinaryDbType(dbType: string): boolean;
|
|
5
5
|
/** Check if a dbType is a native JSON type (json, jsonb). */
|
|
6
6
|
export declare function isJsonDbType(dbType: string): boolean;
|
|
7
|
+
/** Column-level JSON check: native JSON dbType or the JSON GraphQL scalar. */
|
|
8
|
+
export declare function isJsonColumn(col: {
|
|
9
|
+
dbType: string;
|
|
10
|
+
paramType?: string;
|
|
11
|
+
}): boolean;
|
|
7
12
|
/** Check if a dbType is a long text type (text, ntext, varchar(max), xml) */
|
|
8
13
|
export declare function isLongTextDbType(dbType: string): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Column-level "large value" check for fetch-on-demand decisions (grid SELECT
|
|
16
|
+
* exclusion, sortability, content-viewer cells). Prefers the server's
|
|
17
|
+
* dialect-decided `dbColumnSchema.isLargeValue` flag; the dbType-name fallback
|
|
18
|
+
* exists only for servers that predate the flag. The name heuristic is WRONG on
|
|
19
|
+
* PostgreSQL and SQLite — `text` is their ordinary string type, not a LOB — which
|
|
20
|
+
* is exactly why the server flag is authoritative when present.
|
|
21
|
+
*/
|
|
22
|
+
export declare function isLargeValueColumn(col: {
|
|
23
|
+
dbType: string;
|
|
24
|
+
isLargeValue?: boolean;
|
|
25
|
+
}): boolean;
|
|
9
26
|
/** Detect content kind from a string value and optional dbType hint */
|
|
10
27
|
export declare function detectContentKind(value: string, dbType?: string): ContentKind;
|
|
11
28
|
/** Max characters to show in a table cell before truncating */
|
|
@@ -14,3 +31,25 @@ export declare const CELL_TRUNCATE = 80;
|
|
|
14
31
|
export declare function truncateForCell(value: string): string;
|
|
15
32
|
/** Format binary data (base64 string) for display */
|
|
16
33
|
export declare function formatBinaryPreview(value: string): string;
|
|
34
|
+
/** What a binary (base64) value actually IS, sniffed from its magic bytes. */
|
|
35
|
+
export type SniffedBinary = {
|
|
36
|
+
kind: 'image';
|
|
37
|
+
mime: string;
|
|
38
|
+
extension: string;
|
|
39
|
+
} | {
|
|
40
|
+
kind: 'pdf';
|
|
41
|
+
mime: 'application/pdf';
|
|
42
|
+
extension: '.pdf';
|
|
43
|
+
} | {
|
|
44
|
+
kind: 'unknown';
|
|
45
|
+
mime: 'application/octet-stream';
|
|
46
|
+
extension: '.bin';
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Sniffs an image/document type from the FIRST BYTES of a base64 value — never
|
|
50
|
+
* from a column name or a user-supplied mime string, so a mislabeled blob can
|
|
51
|
+
* only ever downgrade to the generic binary rendering, not upgrade to <img>.
|
|
52
|
+
*/
|
|
53
|
+
export declare function sniffBinaryContent(base64: string): SniffedBinary;
|
|
54
|
+
/** A data: URL for an inline binary preview (only call for sniffed images). */
|
|
55
|
+
export declare function binaryDataUrl(base64: string, mime: string): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Column } from '../types/schema';
|
|
2
|
+
/** True for any date-ish column (bare date or datetime). */
|
|
3
|
+
export declare function isDateColumn(column: Column): boolean;
|
|
4
|
+
/** True when a date-ish column carries a time-of-day component (datetime, not a bare date). */
|
|
5
|
+
export declare function isDateTimeColumn(column: Column): boolean;
|
|
6
|
+
/**
|
|
7
|
+
* Normalizes a stored date/datetime string into the value an `<input type="date">`
|
|
8
|
+
* or `<input type="datetime-local">` expects. For datetime columns the time-of-day
|
|
9
|
+
* is preserved (fractional seconds and timezone offset are trimmed) so re-saving an
|
|
10
|
+
* unrelated field does not overwrite the time with midnight. Returns '' for the
|
|
11
|
+
* SQL "zero" date and unparseable input.
|
|
12
|
+
*/
|
|
13
|
+
export declare function toDateInputValue(raw: string | undefined, withTime: boolean): string;
|
|
14
|
+
/**
|
|
15
|
+
* Restore the original raw value for every date/datetime field the user did not
|
|
16
|
+
* edit. The form holds `toDateInputValue` projections; a field whose current form
|
|
17
|
+
* value still equals the projection of the original raw value is untouched, so
|
|
18
|
+
* the raw value (offset and fractional seconds intact) is sent instead of the
|
|
19
|
+
* lossy projection. Fields the user actually changed keep the new form value.
|
|
20
|
+
*/
|
|
21
|
+
export declare function preserveUntouchedDateValues(formValues: Record<string, unknown>, originalRow: Record<string, unknown>, columns: Column[]): Record<string, unknown>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,8 +1,25 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @module lib/drill-stack
|
|
3
|
+
*
|
|
4
|
+
* Pure-logic helpers for the stackable parent/child drill-down used by the
|
|
5
|
+
* edit-db data panel. Each entry in the stack describes a side column: a
|
|
6
|
+
* target table plus an optional filter (FK column + parent row id) that
|
|
7
|
+
* scopes the child rows to those related to a row in the previous level.
|
|
8
|
+
*
|
|
9
|
+
* Kept dependency-free so it can be unit-tested without React or the DOM.
|
|
10
|
+
*/
|
|
2
11
|
/**
|
|
3
12
|
* A single frame in the drill navigation chain, visible as one side column.
|
|
13
|
+
* `filterTable` + `filterId` + `filterColumn` describe the FK scoping applied
|
|
14
|
+
* to `tableName`'s rows — they're optional so the same shape can also
|
|
15
|
+
* represent a simple "open table X" panel.
|
|
4
16
|
*/
|
|
5
|
-
export
|
|
17
|
+
export interface DrillFrame {
|
|
18
|
+
tableName: string;
|
|
19
|
+
filterTable?: string;
|
|
20
|
+
filterId?: string;
|
|
21
|
+
filterColumn?: string;
|
|
22
|
+
}
|
|
6
23
|
/**
|
|
7
24
|
* Is `frame` the same drill target as `other` (same table + same filter)?
|
|
8
25
|
*
|
|
@@ -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
|
@@ -1,13 +1,34 @@
|
|
|
1
|
-
import { Join, Table } from '../types/schema';
|
|
1
|
+
import { Column, Join, Table } from '../types/schema';
|
|
2
2
|
export interface FkEqFilterResult {
|
|
3
3
|
filterText: string;
|
|
4
4
|
variables: Record<string, unknown>;
|
|
5
5
|
params: string[];
|
|
6
6
|
}
|
|
7
|
+
/**
|
|
8
|
+
* Coerce a raw cell/route value to the JS type matching a GraphQL scalar so the
|
|
9
|
+
* variable payload agrees with its declared type (e.g. a Boolean FK is sent as
|
|
10
|
+
* `true`, not `"true"`). Unparseable numerics are passed through untouched so
|
|
11
|
+
* the server reports the real error instead of receiving `NaN`.
|
|
12
|
+
*/
|
|
13
|
+
export declare function coerceForGql(value: unknown, gqlType: string): unknown;
|
|
14
|
+
/** The base GraphQL scalar of a column (`paramType` minus non-null marker), defaulting to String. */
|
|
15
|
+
export declare function gqlTypeOf(column: Column | undefined): string;
|
|
7
16
|
export declare function isFkMember(join: Join, columnName: string): boolean;
|
|
8
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;
|
|
9
31
|
export declare function findJoinBySource(joins: Join[], columnName: string): Join | undefined;
|
|
10
|
-
export declare function findJoinByDestinationTable(joins: Join[], destinationTable: string): Join | undefined;
|
|
11
32
|
/**
|
|
12
33
|
* Build a `{_eq}`-per-column composite filter against the destination side of a join,
|
|
13
34
|
* using values pulled from a source-side row. Single-column joins emit a single
|
|
@@ -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 {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { QueryClient } from '@tanstack/react-query';
|
|
2
|
+
/**
|
|
3
|
+
* Invalidate every query family that can serve rows affected by a write to
|
|
4
|
+
* `tableName`. Returns the refetch promise for callers that want to await it;
|
|
5
|
+
* queries are marked invalidated synchronously either way.
|
|
6
|
+
*/
|
|
7
|
+
export declare function invalidateAfterTableWrite(queryClient: QueryClient, tableName: string): Promise<void>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module lib/label-match
|
|
3
|
+
*
|
|
4
|
+
* Client-side label filter shared by FK dropdowns (`ParentField`) and the
|
|
5
|
+
* many-to-many `TargetPicker` fallback. Used when the label column is not
|
|
6
|
+
* String-typed, so the server-side `_contains` search is unavailable and rows
|
|
7
|
+
* are filtered within the fetched window instead.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Case-insensitive substring match against a row's `label`, falling back to its
|
|
11
|
+
* id field (`idKey`) when no label was fetched.
|
|
12
|
+
*/
|
|
13
|
+
export declare function matchesLabel(row: object, idKey: string, term: string): boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/lib/m2m.d.ts
CHANGED
|
@@ -31,13 +31,45 @@ export declare function m2mRowsQuery(junction: Table, target: Table, m2m: ManyTo
|
|
|
31
31
|
};
|
|
32
32
|
export interface M2mTargetPickerPlan {
|
|
33
33
|
query: string;
|
|
34
|
-
|
|
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;
|
|
47
|
+
/** True when the label column is String-typed and thus supports the
|
|
48
|
+
* server-side `_contains` search; callers fall back to client filtering. */
|
|
49
|
+
serverSearch: boolean;
|
|
35
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[];
|
|
36
64
|
/**
|
|
37
65
|
* Build the target-list query used when adding a many-to-many link.
|
|
38
66
|
* Sort values are BifrostQL enum literals, so they must not be quoted.
|
|
67
|
+
*
|
|
68
|
+
* When `search` is non-empty the list is filtered server-side (a `_contains` on
|
|
69
|
+
* the label column) so a large target table stays findable beyond the fetch
|
|
70
|
+
* limit, instead of searching only within a client-side window.
|
|
39
71
|
*/
|
|
40
|
-
export declare function m2mTargetPickerPlan(target: Table, m2m: ManyToManyJoin): M2mTargetPickerPlan;
|
|
72
|
+
export declare function m2mTargetPickerPlan(target: Table, m2m: ManyToManyJoin, search?: string): M2mTargetPickerPlan;
|
|
41
73
|
/**
|
|
42
74
|
* Read the target row's id and display label from a junction row's nested target
|
|
43
75
|
* selection (emitted by {@link m2mRowsQuery}). Returns null when the link has no
|
|
@@ -53,9 +85,16 @@ export declare function targetDisplay(junctionRow: Record<string, unknown>, m2m:
|
|
|
53
85
|
* the chosen target key on the junction target FK. Any payload columns are left
|
|
54
86
|
* unset so the database applies its defaults; the user edits them afterwards.
|
|
55
87
|
*
|
|
56
|
-
* Single-column junction FKs only. `parentId
|
|
57
|
-
*
|
|
58
|
-
* would be written verbatim into one column —
|
|
59
|
-
* than guess a decomposition, fail loudly until
|
|
88
|
+
* Single-column junction FKs only. `parentId` is a route-encoded string
|
|
89
|
+
* (produced by rowIdOf/encodePkRoute); for a composite FK it carries multiple
|
|
90
|
+
* values joined by "::", which would be written verbatim into one column —
|
|
91
|
+
* silent data corruption. Rather than guess a decomposition, fail loudly until
|
|
92
|
+
* composite-FK support is built.
|
|
93
|
+
*
|
|
94
|
+
* The parent value is percent-DECODED before being written: the read side
|
|
95
|
+
* (m2mRowsQuery) decodes the route id before filtering, so writing the encoded
|
|
96
|
+
* form (e.g. 'caf%C3%A9' for a 'café' key) would insert a dangling FK that the
|
|
97
|
+
* panel's filter never matches. `targetId` is NOT route-encoded — it comes raw
|
|
98
|
+
* from the picker row — so it is written as-is.
|
|
60
99
|
*/
|
|
61
100
|
export declare function attachJunctionDetail(m2m: ManyToManyJoin, parentId: string, targetId: string): Record<string, unknown>;
|