@izumisy-tailor/tailor-data-viewer 0.2.15 → 0.2.17

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/README.md CHANGED
@@ -215,7 +215,7 @@ export default defineConfig({
215
215
  tailor-sdk generate
216
216
  ```
217
217
 
218
- ### `inferColumnHelper(metadata, tableName)`
218
+ ### `inferColumnHelper(tableMetadata)`
219
219
 
220
220
  `field()` requires manually specifying `sort`/`filter` type configs and enum `options` for every column. `inferColumnHelper()` eliminates this boilerplate by automatically deriving these from the generated table metadata. Based on each field's type (string, number, date, enum, etc.), the appropriate `SortConfig` / `FilterConfig` is set automatically, and enum fields get their options populated from the schema.
221
221
 
@@ -223,7 +223,7 @@ tailor-sdk generate
223
223
  import { inferColumnHelper, display } from "@izumisy-tailor/tailor-data-viewer/component";
224
224
  import { tableMetadata } from "./generated/data-viewer-metadata.generated";
225
225
 
226
- const { column, columns } = inferColumnHelper(tableMetadata, "task");
226
+ const { column, columns } = inferColumnHelper(tableMetadata.task);
227
227
 
228
228
  const taskColumns = [
229
229
  column("title"), // sort/filter auto-configured from metadata
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@izumisy-tailor/tailor-data-viewer",
3
3
  "private": false,
4
- "version": "0.2.15",
4
+ "version": "0.2.17",
5
5
  "type": "module",
6
6
  "description": "Flexible data viewer component for Tailor Platform",
7
7
  "files": [
@@ -409,11 +409,10 @@ describe("useCollection", () => {
409
409
  },
410
410
  } as const satisfies TableMetadataMap;
411
411
 
412
- it("works with metadata and tableName", () => {
412
+ it("works with tableMetadata", () => {
413
413
  const { result } = renderHook(() =>
414
414
  useCollection({
415
- metadata: testMetadata,
416
- tableName: "task",
415
+ tableMetadata: testMetadata.task,
417
416
  query: FAKE_QUERY,
418
417
  params: { pageSize: 10 },
419
418
  }),
@@ -424,8 +423,7 @@ describe("useCollection", () => {
424
423
  it("applies typed initialSort", () => {
425
424
  const { result } = renderHook(() =>
426
425
  useCollection({
427
- metadata: testMetadata,
428
- tableName: "task",
426
+ tableMetadata: testMetadata.task,
429
427
  query: FAKE_QUERY,
430
428
  params: {
431
429
  initialSort: [{ field: "dueDate", direction: "Desc" }],
@@ -1,9 +1,9 @@
1
1
  import { useCallback, useMemo, useState } from "react";
2
- import type { TableMetadataMap } from "../../generator/metadata-generator";
2
+ import type { TableMetadata } from "../../generator/metadata-generator";
3
3
  import type {
4
4
  Filter,
5
5
  FilterOperator,
6
- MetadataFilter,
6
+ TableMetadataFilter,
7
7
  PageInfo,
8
8
  QueryVariables,
9
9
  SortState,
@@ -12,7 +12,7 @@ import type {
12
12
  ExtractQueryVariables,
13
13
  ValidateCollectionQuery,
14
14
  } from "../types";
15
- import type { FieldName, OrderableFieldName } from "../types";
15
+ import type { TableFieldName, TableOrderableFieldName } from "../types";
16
16
 
17
17
  /**
18
18
  * Resolves the variables type for `toQueryArgs()` return value.
@@ -47,38 +47,32 @@ type ResolveVariables<TQuery> =
47
47
  * import { tableMetadata } from "./generated/data-viewer-metadata.generated";
48
48
  *
49
49
  * const collection = useCollection({
50
- * metadata: tableMetadata,
51
- * tableName: "task",
50
+ * tableMetadata: tableMetadata.task,
52
51
  * query: GET_TASKS,
53
52
  * params: { pageSize: 20 },
54
53
  * });
55
54
  * const [result] = useQuery({ ...collection.toQueryArgs() });
56
55
  * ```
57
56
  */
58
- export function useCollection<
59
- const TMetadata extends TableMetadataMap,
60
- TTableName extends string & keyof TMetadata,
61
- TQuery,
62
- >(
57
+ export function useCollection<const TTable extends TableMetadata, TQuery>(
63
58
  options: UseCollectionOptions<
64
- FieldName<TMetadata, TTableName>,
65
- MetadataFilter<TMetadata, TTableName>
59
+ TableFieldName<TTable>,
60
+ TableMetadataFilter<TTable>
66
61
  > & {
67
- metadata: TMetadata;
68
- tableName: TTableName;
62
+ tableMetadata: TTable;
69
63
  query: ValidateCollectionQuery<
70
64
  TQuery,
71
- FieldName<TMetadata, TTableName>,
72
- OrderableFieldName<TMetadata, TTableName>
65
+ TableFieldName<TTable>,
66
+ TableOrderableFieldName<TTable>
73
67
  >;
74
68
  },
75
69
  ): UseCollectionReturn<
76
- FieldName<TMetadata, TTableName>,
70
+ TableFieldName<TTable>,
77
71
  {
78
72
  query: TQuery;
79
73
  variables: ResolveVariables<TQuery>;
80
74
  },
81
- MetadataFilter<TMetadata, TTableName>
75
+ TableMetadataFilter<TTable>
82
76
  >;
83
77
 
84
78
  /**
@@ -99,8 +93,7 @@ export function useCollection<
99
93
  export function useCollection<TQuery>(
100
94
  options: UseCollectionOptions & {
101
95
  query: TQuery;
102
- metadata?: never;
103
- tableName?: never;
96
+ tableMetadata?: never;
104
97
  },
105
98
  ): UseCollectionReturn<
106
99
  string,
@@ -112,8 +105,7 @@ export function useCollection<TQuery>(
112
105
  // -----------------------------------------------------------------------------
113
106
  export function useCollection(
114
107
  options: UseCollectionOptions & {
115
- metadata?: TableMetadataMap;
116
- tableName?: string;
108
+ tableMetadata?: TableMetadata;
117
109
  query: unknown;
118
110
  },
119
111
  ): UseCollectionReturn<string, { query: unknown; variables: QueryVariables }> {
@@ -1,4 +1,4 @@
1
- import { useCallback, useMemo, useState } from "react";
1
+ import { useCallback, useEffect, useMemo, useState } from "react";
2
2
  import type {
3
3
  Column,
4
4
  DataTableCellProps,
@@ -54,7 +54,7 @@ export function useDataTable<TRow extends Record<string, unknown>>(
54
54
  const rows = optimisticRows ?? sourceRows;
55
55
 
56
56
  // Reset optimistic state when source data changes
57
- useMemo(() => {
57
+ useEffect(() => {
58
58
  setOptimisticRows(null);
59
59
  }, [sourceRows]);
60
60
 
@@ -70,11 +70,11 @@ export function useDataTable<TRow extends Record<string, unknown>>(
70
70
  }, [data]);
71
71
 
72
72
  // Sync pageInfo to collection so hasPrevPage/hasNextPage are up-to-date
73
- useMemo(() => {
73
+ useEffect(() => {
74
74
  if (data?.pageInfo) {
75
75
  collection?.setPageInfo(data.pageInfo);
76
76
  }
77
- }, [data?.pageInfo, collection]);
77
+ }, [data?.pageInfo, collection?.setPageInfo]);
78
78
 
79
79
  // ---------------------------------------------------------------------------
80
80
  // Column visibility management
@@ -2,7 +2,7 @@ import { describe, it, expect, expectTypeOf } from "vitest";
2
2
  import { createColumnHelper, inferColumnHelper } from "./field-helpers";
3
3
  import type { TableMetadataMap } from "../generator/metadata-generator";
4
4
  import { fieldTypeToSortConfig, fieldTypeToFilterConfig } from "./types";
5
- import type { NodeType } from "./types";
5
+ import type { NodeType, TableFieldName } from "./types";
6
6
 
7
7
  describe("NodeType", () => {
8
8
  it("extracts node type from a collection result", () => {
@@ -167,10 +167,7 @@ describe("inferColumnHelper()", () => {
167
167
  } as const satisfies TableMetadataMap;
168
168
 
169
169
  it("creates a column with auto-detected sort/filter", () => {
170
- const { column } = inferColumnHelper({
171
- metadata: testMetadata,
172
- tableName: "task",
173
- });
170
+ const { column } = inferColumnHelper(testMetadata.task);
174
171
 
175
172
  const titleCol = column("title");
176
173
  expect(titleCol.kind).toBe("field");
@@ -180,10 +177,7 @@ describe("inferColumnHelper()", () => {
180
177
  });
181
178
 
182
179
  it("auto-detects enum options", () => {
183
- const { column } = inferColumnHelper({
184
- metadata: testMetadata,
185
- tableName: "task",
186
- });
180
+ const { column } = inferColumnHelper(testMetadata.task);
187
181
  const statusCol = column("status");
188
182
  expect(statusCol.filter).toEqual({
189
183
  type: "enum",
@@ -198,60 +192,42 @@ describe("inferColumnHelper()", () => {
198
192
  });
199
193
 
200
194
  it("auto-detects date type", () => {
201
- const { column } = inferColumnHelper({
202
- metadata: testMetadata,
203
- tableName: "task",
204
- });
195
+ const { column } = inferColumnHelper(testMetadata.task);
205
196
  const dateCol = column("dueDate");
206
197
  expect(dateCol.sort).toEqual({ type: "date" });
207
198
  expect(dateCol.filter).toEqual({ type: "date" });
208
199
  });
209
200
 
210
201
  it("disables sort with sort: false", () => {
211
- const { column } = inferColumnHelper({
212
- metadata: testMetadata,
213
- tableName: "task",
214
- });
202
+ const { column } = inferColumnHelper(testMetadata.task);
215
203
  const col = column("title", { sort: false });
216
204
  expect(col.sort).toBeUndefined();
217
205
  expect(col.filter).toEqual({ type: "string" });
218
206
  });
219
207
 
220
208
  it("disables filter with filter: false", () => {
221
- const { column } = inferColumnHelper({
222
- metadata: testMetadata,
223
- tableName: "task",
224
- });
209
+ const { column } = inferColumnHelper(testMetadata.task);
225
210
  const col = column("title", { filter: false });
226
211
  expect(col.sort).toEqual({ type: "string" });
227
212
  expect(col.filter).toBeUndefined();
228
213
  });
229
214
 
230
215
  it("uuid has no sort, has uuid filter", () => {
231
- const { column } = inferColumnHelper({
232
- metadata: testMetadata,
233
- tableName: "task",
234
- });
216
+ const { column } = inferColumnHelper(testMetadata.task);
235
217
  const col = column("id");
236
218
  expect(col.sort).toBeUndefined();
237
219
  expect(col.filter).toEqual({ type: "uuid" });
238
220
  });
239
221
 
240
222
  it("array type has no sort/filter", () => {
241
- const { column } = inferColumnHelper({
242
- metadata: testMetadata,
243
- tableName: "task",
244
- });
223
+ const { column } = inferColumnHelper(testMetadata.task);
245
224
  const col = column("tags");
246
225
  expect(col.sort).toBeUndefined();
247
226
  expect(col.filter).toBeUndefined();
248
227
  });
249
228
 
250
229
  it("columns() creates multiple columns at once", () => {
251
- const { columns } = inferColumnHelper({
252
- metadata: testMetadata,
253
- tableName: "task",
254
- });
230
+ const { columns } = inferColumnHelper(testMetadata.task);
255
231
  const cols = columns(["title", "status", "dueDate"]);
256
232
  expect(cols).toHaveLength(3);
257
233
  expect(cols[0].dataKey).toBe("title");
@@ -260,10 +236,7 @@ describe("inferColumnHelper()", () => {
260
236
  });
261
237
 
262
238
  it("columns() applies overrides", () => {
263
- const { columns } = inferColumnHelper({
264
- metadata: testMetadata,
265
- tableName: "task",
266
- });
239
+ const { columns } = inferColumnHelper(testMetadata.task);
267
240
  const cols = columns(["title", "status"], {
268
241
  overrides: {
269
242
  title: { label: "Custom Title", width: 300 },
@@ -274,10 +247,7 @@ describe("inferColumnHelper()", () => {
274
247
  });
275
248
 
276
249
  it("columns() applies global sort/filter options", () => {
277
- const { columns } = inferColumnHelper({
278
- metadata: testMetadata,
279
- tableName: "task",
280
- });
250
+ const { columns } = inferColumnHelper(testMetadata.task);
281
251
  const cols = columns(["title", "status"], { sort: false });
282
252
  expect(cols[0].sort).toBeUndefined();
283
253
  expect(cols[1].sort).toBeUndefined();
@@ -286,13 +256,17 @@ describe("inferColumnHelper()", () => {
286
256
  });
287
257
 
288
258
  it("throws for non-existent field", () => {
289
- const { column } = inferColumnHelper({
290
- metadata: testMetadata,
291
- tableName: "task",
292
- });
259
+ const { column } = inferColumnHelper(testMetadata.task);
293
260
  // @ts-expect-error - intentionally testing invalid field name
294
261
  expect(() => column("nonExistent")).toThrow(
295
262
  'Field "nonExistent" not found in table "task" metadata',
296
263
  );
297
264
  });
265
+
266
+ it("infers TableFieldName type correctly", () => {
267
+ type TaskFieldNames = TableFieldName<(typeof testMetadata)["task"]>;
268
+ expectTypeOf<TaskFieldNames>().toEqualTypeOf<
269
+ "id" | "title" | "status" | "dueDate" | "count" | "isActive" | "tags"
270
+ >();
271
+ });
298
272
  });
@@ -1,11 +1,11 @@
1
- import type { TableMetadataMap } from "../generator/metadata-generator";
1
+ import type { TableMetadata } from "../generator/metadata-generator";
2
2
  import type {
3
3
  CellRenderer,
4
4
  DisplayColumn,
5
5
  DisplayColumnOptions,
6
6
  FieldColumn,
7
7
  FieldColumnOptions,
8
- FieldName,
8
+ TableFieldName,
9
9
  FilterConfig,
10
10
  MetadataFieldOptions,
11
11
  MetadataFieldsOptions,
@@ -130,16 +130,13 @@ export function createColumnHelper<TRow extends Record<string, unknown>>(): {
130
130
  * Automatically infers sort/filter configuration from field types,
131
131
  * including enum options.
132
132
  *
133
- * @param options - Object containing `metadata` (the generated map) and `tableName`.
133
+ * @param tableMetadata - A single table metadata object from the generated map.
134
134
  *
135
135
  * @example
136
136
  * ```tsx
137
137
  * import { tableMetadata } from "./generated/data-viewer-metadata.generated";
138
138
  *
139
- * const { column, columns } = inferColumnHelper({
140
- * metadata: tableMetadata,
141
- * tableName: "task",
142
- * });
139
+ * const { column, columns } = inferColumnHelper(tableMetadata.task);
143
140
  *
144
141
  * const taskColumns = [
145
142
  * column("title"), // sort/filter auto-detected
@@ -149,35 +146,29 @@ export function createColumnHelper<TRow extends Record<string, unknown>>(): {
149
146
  * ];
150
147
  * ```
151
148
  */
152
- export function inferColumnHelper<
153
- const TMetadata extends TableMetadataMap,
154
- TTableName extends string & keyof TMetadata,
155
- >(options: {
156
- metadata: TMetadata;
157
- tableName: TTableName;
158
- }): {
149
+ export function inferColumnHelper<const TTable extends TableMetadata>(
150
+ tableMetadata: TTable,
151
+ ): {
159
152
  column: (
160
- dataKey: FieldName<TMetadata, TTableName>,
153
+ dataKey: TableFieldName<TTable>,
161
154
  options?: MetadataFieldOptions,
162
155
  ) => FieldColumn<Record<string, unknown>>;
163
156
 
164
157
  columns: (
165
- dataKeys: FieldName<TMetadata, TTableName>[],
158
+ dataKeys: TableFieldName<TTable>[],
166
159
  options?: MetadataFieldsOptions,
167
160
  ) => FieldColumn<Record<string, unknown>>[];
168
161
  } {
169
- const { metadata, tableName } = options;
170
- const tableMeta = metadata[tableName];
171
- const fields = tableMeta.fields;
162
+ const fields = tableMetadata.fields;
172
163
 
173
164
  const column = (
174
- dataKey: FieldName<TMetadata, TTableName>,
165
+ dataKey: TableFieldName<TTable>,
175
166
  columnOptions?: MetadataFieldOptions,
176
167
  ): FieldColumn<Record<string, unknown>> => {
177
168
  const fieldMeta = fields.find((f) => f.name === dataKey);
178
169
  if (!fieldMeta) {
179
170
  throw new Error(
180
- `Field "${String(dataKey)}" not found in table "${String(tableName)}" metadata`,
171
+ `Field "${String(dataKey)}" not found in table "${tableMetadata.name}" metadata`,
181
172
  );
182
173
  }
183
174
 
@@ -213,7 +204,7 @@ export function inferColumnHelper<
213
204
  };
214
205
 
215
206
  const columnsHelper = (
216
- dataKeys: FieldName<TMetadata, TTableName>[],
207
+ dataKeys: TableFieldName<TTable>[],
217
208
  options?: MetadataFieldsOptions,
218
209
  ): FieldColumn<Record<string, unknown>>[] => {
219
210
  return dataKeys.map((dataKey) => {
@@ -5,7 +5,6 @@ export type {
5
5
  SelectOption,
6
6
  FilterOperator,
7
7
  Filter,
8
- MetadataFilter,
9
8
  SortState,
10
9
  PageInfo,
11
10
  QueryVariables,
@@ -28,7 +27,9 @@ export type {
28
27
  DataTableRowProps,
29
28
  DataTableCellProps,
30
29
  FieldName,
30
+ TableFieldName,
31
31
  OrderableFieldName,
32
+ TableOrderableFieldName,
32
33
  ExtractOrderField,
33
34
  ExtractQueryVariables,
34
35
  ExtractQueryInputKeys,
@@ -37,6 +38,8 @@ export type {
37
38
  MatchingTableName,
38
39
  MetadataFieldOptions,
39
40
  MetadataFieldsOptions,
41
+ MetadataFilter,
42
+ TableMetadataFilter,
40
43
  ColumnSelectorProps,
41
44
  CsvButtonProps,
42
45
  SearchFilterFormProps,
@@ -1,6 +1,7 @@
1
1
  import type { ReactNode } from "react";
2
2
  import type {
3
3
  FieldType,
4
+ TableMetadata,
4
5
  TableMetadataMap,
5
6
  } from "../generator/metadata-generator";
6
7
 
@@ -144,24 +145,40 @@ export interface Filter<TFieldName extends string = string> {
144
145
  * // | { field: "status"; operator: "eq" | "ne" | "in" | "notIn"; value: unknown }
145
146
  * ```
146
147
  */
148
+ /**
149
+ * Metadata-aware filter type from a single table metadata object.
150
+ *
151
+ * Given table metadata, produces a discriminated union over each field
152
+ * where the `operator` is narrowed to only those valid for that field's type.
153
+ *
154
+ * @typeParam TTable - A single table metadata object (as const).
155
+ */
156
+ export type TableMetadataFilter<TTable extends TableMetadata> =
157
+ TTable["fields"][number] extends infer F
158
+ ? F extends { readonly name: infer N; readonly type: infer T }
159
+ ? N extends string
160
+ ? T extends keyof FieldTypeToFilterConfigType
161
+ ? FieldTypeToFilterConfigType[T] extends never
162
+ ? never
163
+ : {
164
+ field: N;
165
+ operator: OperatorForFilterType[FieldTypeToFilterConfigType[T]];
166
+ value: unknown;
167
+ }
168
+ : never
169
+ : never
170
+ : never
171
+ : never;
172
+
173
+ /**
174
+ * Metadata-aware filter type.
175
+ *
176
+ * @deprecated Use `TableMetadataFilter<TMetadata[TTableName]>` instead.
177
+ */
147
178
  export type MetadataFilter<
148
179
  TMetadata extends TableMetadataMap,
149
180
  TTableName extends string & keyof TMetadata,
150
- > = TMetadata[TTableName]["fields"][number] extends infer F
151
- ? F extends { readonly name: infer N; readonly type: infer T }
152
- ? N extends string
153
- ? T extends keyof FieldTypeToFilterConfigType
154
- ? FieldTypeToFilterConfigType[T] extends never
155
- ? never
156
- : {
157
- field: N;
158
- operator: OperatorForFilterType[FieldTypeToFilterConfigType[T]];
159
- value: unknown;
160
- }
161
- : never
162
- : never
163
- : never
164
- : never;
181
+ > = TableMetadataFilter<TMetadata[TTableName]>;
165
182
 
166
183
  /**
167
184
  * Active sort state for a single field.
@@ -599,16 +616,30 @@ export interface UseDataTableReturn<TRow extends Record<string, unknown>> {
599
616
  // =============================================================================
600
617
 
601
618
  /**
602
- * Extract field names from table metadata.
619
+ * Extract field names from a single table metadata object.
620
+ *
621
+ * @example
622
+ * ```ts
623
+ * type Names = TableFieldName<typeof tableMetadata.task>;
624
+ * // → "id" | "title" | "status" | "dueDate" | ...
625
+ * ```
626
+ */
627
+ export type TableFieldName<TTable extends TableMetadata> =
628
+ TTable["fields"][number] extends { readonly name: infer N }
629
+ ? N extends string
630
+ ? N
631
+ : never
632
+ : never;
633
+
634
+ /**
635
+ * Extract field names from table metadata map + table name.
636
+ *
637
+ * @deprecated Use `TableFieldName<TMetadata[TTableName]>` instead.
603
638
  */
604
639
  export type FieldName<
605
640
  TMetadata extends TableMetadataMap,
606
641
  TTableName extends keyof TMetadata,
607
- > = TMetadata[TTableName]["fields"][number] extends { readonly name: infer N }
608
- ? N extends string
609
- ? N
610
- : never
611
- : never;
642
+ > = TableFieldName<TMetadata[TTableName]>;
612
643
 
613
644
  /**
614
645
  * Field types that support ordering.
@@ -625,19 +656,16 @@ type OrderableFieldType =
625
656
  | "enum";
626
657
 
627
658
  /**
628
- * Extract only orderable field names from table metadata.
659
+ * Extract only orderable field names from a single table metadata object.
629
660
  *
630
661
  * Fields whose `type` is not in `OrderableFieldType` (e.g. `uuid`, `array`,
631
662
  * `nested`, `file`) are excluded. This allows `CheckOrderField` to compare
632
663
  * only the fields that Tailor Platform actually exposes in the
633
664
  * `OrderFieldEnum`, avoiding false positives for fields like `id`.
634
665
  */
635
- export type OrderableFieldName<
636
- TMetadata extends TableMetadataMap,
637
- TTableName extends keyof TMetadata,
638
- > =
666
+ export type TableOrderableFieldName<TTable extends TableMetadata> =
639
667
  Extract<
640
- TMetadata[TTableName]["fields"][number],
668
+ TTable["fields"][number],
641
669
  { readonly type: OrderableFieldType }
642
670
  > extends { readonly name: infer N }
643
671
  ? N extends string
@@ -645,6 +673,16 @@ export type OrderableFieldName<
645
673
  : never
646
674
  : never;
647
675
 
676
+ /**
677
+ * Extract only orderable field names from table metadata map + table name.
678
+ *
679
+ * @deprecated Use `TableOrderableFieldName<TMetadata[TTableName]>` instead.
680
+ */
681
+ export type OrderableFieldName<
682
+ TMetadata extends TableMetadataMap,
683
+ TTableName extends keyof TMetadata,
684
+ > = TableOrderableFieldName<TMetadata[TTableName]>;
685
+
648
686
  /**
649
687
  * Extract the `order[].field` union type from a GraphQL variables type.
650
688
  *