@updog/data-editor 0.1.59 → 0.1.61

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 (5) hide show
  1. package/README.md +3 -3
  2. package/index.css +1 -1
  3. package/index.d.ts +111 -11
  4. package/index.js +2771 -2222
  5. package/package.json +1 -1
package/index.d.ts CHANGED
@@ -376,6 +376,7 @@ declare var export_default = {
376
376
  redo: "Redo",
377
377
  },
378
378
  validation: {
379
+ alreadyExists: "Already exists in your database",
379
380
  endDateBeforeStart: "End date must be after start date",
380
381
  invalidDate: "Invalid date",
381
382
  invalidEmail: "Invalid email address",
@@ -650,6 +651,38 @@ type BuiltInValidator = {
650
651
  } | {
651
652
  type: "numeric";
652
653
  message?: string;
654
+ } | {
655
+ type: "unique";
656
+ message?: string;
657
+ fn?: UniqueRemoteFn;
658
+ };
659
+ /** One cell in an asyncFunction batch. Row is passed by reference. */
660
+ type AsyncValidatorCell = {
661
+ /** Value of the validated column for this cell. */
662
+ value: unknown;
663
+ /** The full row — for row-dependent checks. */
664
+ row: DataEditorRow;
665
+ };
666
+ /**
667
+ * Remote existence check for `{ type: "unique" }`. Called once per sweep
668
+ * with all distinct candidate values; report the subset that already exists —
669
+ * return it and/or stream it via onChunk (results are unioned). `signal`
670
+ * fires only when results can no longer be used. Client-mode only.
671
+ */
672
+ type UniqueRemoteFn = (values: unknown[], onChunk: (existing: unknown[]) => void, signal: AbortSignal) => Promise<unknown[] | void>;
673
+ type AsyncFunctionValidator = {
674
+ type: "asyncFunction";
675
+ /**
676
+ * Called once per column per operation with every affected cell. Report
677
+ * failures by `index` into `cells`: return an array aligned with the input
678
+ * (null = valid) and/or stream sparse failures via onChunk (unioned, any
679
+ * order). `signal` fires only when results can no longer be used.
680
+ * Client-mode only. For uniqueness use `unique.fn`, not this.
681
+ */
682
+ fn: (cells: AsyncValidatorCell[], onChunk: (failures: {
683
+ index: number;
684
+ error: ValidationError;
685
+ }[]) => void, signal: AbortSignal) => Promise<(ValidationError | null)[] | void>;
653
686
  };
654
687
  /**
655
688
  * Server-mode-only escape hatch: an expression evaluated by the Go binary
@@ -676,7 +709,13 @@ type FunctionValidator = {
676
709
  * Named `ValidatorRule` (not `Validator`) so it doesn't clash with the
677
710
  * runtime `Validator` class in `core/Validator.ts`.
678
711
  */
679
- type ValidatorRule = BuiltInValidator | ExpressionValidator | FunctionValidator;
712
+ type ValidatorRule = BuiltInValidator | ExpressionValidator | FunctionValidator | AsyncFunctionValidator;
713
+ /**
714
+ * Async-check state of a cell, orthogonal to errors/misplaced: "pending"
715
+ * while a verdict is awaited, "unverified" when the check failed or timed
716
+ * out. Idle cells carry no entry.
717
+ */
718
+ type CellAsyncState = "pending" | "unverified";
680
719
 
681
720
  /**
682
721
  * Severity level for a validation message.
@@ -808,7 +847,7 @@ type ColumnLockMode = "all" | "default";
808
847
  * ```ts
809
848
  * const columns: DataEditorColumn[] = [
810
849
  * { id: "name", title: "Full Name", size: 200, validators: [{ type: "required", message: "Name is required" }] },
811
- * { id: "email", title: "Email", size: 250, validators: [{ type: "required", message: "Email is required" }, { type: "email", message: "Invalid email" }], unique: true },
850
+ * { id: "email", title: "Email", size: 250, validators: [{ type: "required", message: "Email is required" }, { type: "email", message: "Invalid email" }, { type: "unique" }] },
812
851
  * { id: "role", title: "Role", editor: { type: "select", options: ["Admin", "Editor", "Viewer"] } },
813
852
  * { id: "salary", title: "Salary", validators: [{ type: "numeric", message: "Must be a number" }], formatter: (v) => v ? `$${v}` : "" },
814
853
  * ];
@@ -821,19 +860,13 @@ type DataEditorColumn = {
821
860
  title: string;
822
861
  /**
823
862
  * One or more validators run on every edit. Accepts:
824
- * - Built-in object literals: `{ type: "required" | "email" | "regex" | "range" | "oneOf" | "date" | "numeric", ... }`.
863
+ * - Built-in object literals: `{ type: "required" | "email" | "regex" | "range" | "oneOf" | "date" | "numeric" | "unique", ... }`.
825
864
  * - `{ type: "expression", expr }` — server-mode only. Warned in client mode.
826
865
  * - `{ type: "function", fn }` — client-mode only. Dropped+warned in server mode.
827
866
  *
828
867
  * See `_specs/VALIDATIONS.md` §4.5 for the per-mode support matrix.
829
868
  */
830
869
  validators?: ValidatorRule[];
831
- /**
832
- * When `true`, the editor flags duplicate values in this column as errors.
833
- * The error message is localized via the `translations` prop
834
- * (`dataEditor.validation.valueMustBeUnique`).
835
- */
836
- unique?: boolean;
837
870
  /**
838
871
  * Column IDs to revalidate when this column changes. Use for cross-field
839
872
  * rules like "end date must be after start date".
@@ -1470,6 +1503,21 @@ declare class ErrorHandler {
1470
1503
  handleError(error: UpdogError): void;
1471
1504
  }
1472
1505
 
1506
+ type UniqueConfig = {
1507
+ message?: string;
1508
+ fn?: UniqueRemoteFn;
1509
+ };
1510
+ /**
1511
+ * Core-internal column shape produced by normalizeUniqueColumns. Uniqueness
1512
+ * lives in `uniqueConfig` and asyncFunction rules in `asyncConfig` — the
1513
+ * single internal sources of truth — never as entries in `validators`.
1514
+ * Never exported from the package entry.
1515
+ */
1516
+ type NormalizedColumn = DataEditorColumn & {
1517
+ uniqueConfig?: UniqueConfig;
1518
+ asyncConfig?: AsyncFunctionValidator[];
1519
+ };
1520
+
1473
1521
  type FormulaCellContext = {
1474
1522
  value: unknown;
1475
1523
  field: string;
@@ -2023,6 +2071,7 @@ interface SnapshotStateReader {
2023
2071
  hasColumnScoping(): boolean;
2024
2072
  getSortState(): SortState;
2025
2073
  getShowOnlyDeletedRows(): boolean;
2074
+ getPendingValidationCount(): number;
2026
2075
  /** Server-provided aggregate counts. Present only in server mode. */
2027
2076
  getServerEditedCount?(): number;
2028
2077
  getServerNewCount?(): number;
@@ -2095,10 +2144,15 @@ type ValidationDelta = {
2095
2144
  errorDelta: number;
2096
2145
  rowErrorChanged: boolean;
2097
2146
  rowMisplacedChanged: boolean;
2147
+ pendingDelta: number;
2098
2148
  };
2099
2149
  type IValidationStore = {
2100
2150
  setCellValidation(rowId: TRowId, field: string, result: ValidationResult): ValidationDelta;
2101
2151
  getCellValidation(rowId: TRowId, field: string): ValidationResult;
2152
+ setCellAsyncState(rowId: TRowId, field: string, state: CellAsyncState | null): ValidationDelta;
2153
+ getCellAsyncState(rowId: TRowId, field: string): CellAsyncState | null;
2154
+ getPendingCount(): number;
2155
+ getRowsWithPending(): ReadonlySet<TRowId>;
2102
2156
  clearRowValidations(rowId: TRowId): void;
2103
2157
  hasRowErrors(rowId: TRowId): boolean;
2104
2158
  getRowsWithErrors(): ReadonlySet<TRowId>;
@@ -2114,6 +2168,40 @@ type IValidationStore = {
2114
2168
  clear(): void;
2115
2169
  };
2116
2170
 
2171
+ /**
2172
+ * AsyncValidationScheduler — facade over the async annotation channel.
2173
+ *
2174
+ * Owns: per-cell version stamps (the ONLY staleness arbiter), pending /
2175
+ * unverified transitions, edit debounce (SweepQueue), inactivity timeout,
2176
+ * once-per-sweep error reporting, and throttled notify. Checks
2177
+ * (UniqueRemoteCheck / FunctionCheck) own only the client protocol.
2178
+ *
2179
+ * No cancellation — last write wins. Sweeps are never aborted by other
2180
+ * sweeps; every dispatched sweep runs to completion. Each enqueue stamps its
2181
+ * cells with a fresh token; a sweep may touch a cell (state transitions,
2182
+ * asyncFunction error writes) only while its recorded token is current.
2183
+ * Unique error writes go through the live index instead (same last-state-
2184
+ * wins property). The AbortSignal handed to the client fn fires only on
2185
+ * sweep timeout and destroy — a courtesy, never a correctness mechanism.
2186
+ */
2187
+
2188
+ type AsyncCellRequest = {
2189
+ rowId: TRowId;
2190
+ field: string;
2191
+ value: unknown;
2192
+ };
2193
+ type EnqueueOptions = {
2194
+ bulk?: boolean;
2195
+ };
2196
+ /** What Validator sees — one method, never the class (ValidatorDataAccess idiom). */
2197
+ type AsyncValidationPort = {
2198
+ enqueue(requests: AsyncCellRequest[], opts?: EnqueueOptions): void;
2199
+ };
2200
+ type IAsyncValidationScheduler = AsyncValidationPort & {
2201
+ flush(): void;
2202
+ destroy(): void;
2203
+ };
2204
+
2117
2205
  type IValidator<TRow extends DataEditorRow = DataEditorRow> = {
2118
2206
  validateRow(rowId: TRowId): void;
2119
2207
  validateRows(rows: TRow[], rowIds: TRowId[]): void;
@@ -2123,6 +2211,7 @@ type IValidator<TRow extends DataEditorRow = DataEditorRow> = {
2123
2211
  validateColumn(field: string, oldValues: ReadonlyMap<TRowId, unknown>): void;
2124
2212
  revalidateColumnChunked(field: string, oldValues: ReadonlyMap<TRowId, unknown>, newValues: ReadonlyMap<TRowId, unknown>, processor: ChunkedProcessor<TRowId>, onComplete: () => void): void;
2125
2213
  removeRow(rowId: TRowId): void;
2214
+ setAsyncPort(port: AsyncValidationPort): void;
2126
2215
  destroy(): void;
2127
2216
  };
2128
2217
 
@@ -2271,6 +2360,7 @@ declare class DataStore<TRow extends DataEditorRow = DataEditorRow> {
2271
2360
  private dirtyTracker;
2272
2361
  private filterEngine;
2273
2362
  private validationStore;
2363
+ private asyncScheduler;
2274
2364
  private snapshotManager;
2275
2365
  private history;
2276
2366
  private validator;
@@ -2343,8 +2433,8 @@ declare class DataStore<TRow extends DataEditorRow = DataEditorRow> {
2343
2433
  private withAddedOptions;
2344
2434
  private rebuildEffectiveColumns;
2345
2435
  setSchemaColumns(columns: DataEditorColumn[]): void;
2346
- getDynamicColumns(): DataEditorColumn[];
2347
- getEffectiveColumns: () => DataEditorColumn[];
2436
+ getDynamicColumns(): NormalizedColumn[];
2437
+ getEffectiveColumns: () => NormalizedColumn[];
2348
2438
  setDynamicColumns(fn: (prev: DataEditorColumn[]) => DataEditorColumn[]): void;
2349
2439
  addDynamicColumns(columns: DataEditorColumn[]): void;
2350
2440
  addColumnOptions(columnId: string, values: string[]): void;
@@ -2511,6 +2601,9 @@ declare class DataStore<TRow extends DataEditorRow = DataEditorRow> {
2511
2601
  syncWorkerFlags(): void;
2512
2602
  setCellValidation(rowId: TRowId, field: string, result: ValidationResult): void;
2513
2603
  getCellValidation(rowId: TRowId, field: string): ValidationResult;
2604
+ setAsyncScheduler(scheduler: IAsyncValidationScheduler): void;
2605
+ setCellAsyncState(rowId: TRowId, field: string, state: CellAsyncState | null): void;
2606
+ getCellAsyncState(rowId: TRowId, field: string): CellAsyncState | null;
2514
2607
  /** Strips "misplaced" from one cell, keeping any error-level entries. */
2515
2608
  private clearCellMisplaced;
2516
2609
  /**
@@ -2547,6 +2640,8 @@ declare class DataStore<TRow extends DataEditorRow = DataEditorRow> {
2547
2640
  getOriginalCellValue(rowId: TRowId, field: string): unknown | undefined;
2548
2641
  isCellDirty(rowId: TRowId, field: string): boolean;
2549
2642
  hasRowErrors(rowId: TRowId): boolean;
2643
+ /** O(1) row gate for the grid's pending-stripe pass. */
2644
+ hasRowPending(rowId: TRowId): boolean;
2550
2645
  hasEmptyCells(rowId: TRowId): boolean;
2551
2646
  hasRowMisplaced(rowId: TRowId): boolean;
2552
2647
  getRowsWithMisplaced(): ReadonlySet<TRowId>;
@@ -2836,6 +2931,10 @@ type DataStoreSnapshot = {
2836
2931
  filteredEmptyRowCount: number;
2837
2932
  /** Visible non-deleted rows with a cell flagged as misplaced after ragged-row realignment. */
2838
2933
  misplacedRowCount: number;
2934
+ /** Cells currently awaiting an async validation verdict. */
2935
+ pendingValidationCount: number;
2936
+ /** True while any async check is queued or in flight — drives submit gating. */
2937
+ isAsyncValidating: boolean;
2839
2938
  isLoading: boolean;
2840
2939
  isFiltering: boolean;
2841
2940
  /** Unified phase state machine. Controls what actions are allowed. */
@@ -2843,6 +2942,7 @@ type DataStoreSnapshot = {
2843
2942
  /** Details about the in-progress operation. Null when phase is "idle". */
2844
2943
  processing: ProcessingInfo | null;
2845
2944
  sources: DataSourceState[];
2945
+ hasBaselineData: boolean;
2846
2946
  canUndo: boolean;
2847
2947
  canRedo: boolean;
2848
2948
  /** Increments on undo/redo to force canvas grid re-render */