@izumisy-tailor/tailor-data-viewer 0.1.2 → 0.1.3
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/API.md +137 -0
- package/README.md +27 -144
- package/dist/generator/index.d.mts +170 -0
- package/dist/generator/index.mjs +161 -0
- package/package.json +14 -7
package/API.md
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# API Reference
|
|
2
|
+
|
|
3
|
+
## Components
|
|
4
|
+
|
|
5
|
+
### `<DataViewer />`
|
|
6
|
+
|
|
7
|
+
Main component with tab-based interface.
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
interface DataViewerProps {
|
|
11
|
+
/** Map of table name to metadata */
|
|
12
|
+
tableMetadata: TableMetadataMap;
|
|
13
|
+
/** User's roles for access control */
|
|
14
|
+
userRoles: string[];
|
|
15
|
+
/** GraphQL endpoint URI */
|
|
16
|
+
appUri: string;
|
|
17
|
+
/** Optional initial view ID to load */
|
|
18
|
+
initialViewId?: string;
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### `<SavedViewProvider />`
|
|
23
|
+
|
|
24
|
+
Context provider for view persistence (localStorage-based).
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
<SavedViewProvider>
|
|
28
|
+
<DataViewer {...props} />
|
|
29
|
+
</SavedViewProvider>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### `<DataTable />`
|
|
33
|
+
|
|
34
|
+
Core data table component with sortable columns and expandable relations.
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
interface DataTableProps {
|
|
38
|
+
data: Record<string, unknown>[];
|
|
39
|
+
fields: FieldMetadata[];
|
|
40
|
+
selectedFields: string[];
|
|
41
|
+
sortState: SortState | null;
|
|
42
|
+
onSort: (field: string) => void;
|
|
43
|
+
loading?: boolean;
|
|
44
|
+
tableMetadata?: TableMetadata;
|
|
45
|
+
tableMetadataMap?: TableMetadataMap;
|
|
46
|
+
appUri?: string;
|
|
47
|
+
selectedRelations?: string[];
|
|
48
|
+
onOpenAsSheet?: (tableName: string, filterField: string, filterValue: string) => void;
|
|
49
|
+
onOpenSingleRecordAsSheet?: (tableName: string, recordId: string) => void;
|
|
50
|
+
expandedRelationFields?: ExpandedRelationFields;
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Hooks
|
|
55
|
+
|
|
56
|
+
### `useTableData`
|
|
57
|
+
|
|
58
|
+
Manages table data fetching with pagination, sorting, and filtering.
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
const {
|
|
62
|
+
data,
|
|
63
|
+
loading,
|
|
64
|
+
error,
|
|
65
|
+
pagination,
|
|
66
|
+
sortState,
|
|
67
|
+
setSort,
|
|
68
|
+
nextPage,
|
|
69
|
+
previousPage,
|
|
70
|
+
resetPagination,
|
|
71
|
+
refetch,
|
|
72
|
+
} = useTableData(appUri, table, selectedFields, selectedRelations, filters, metadataMap, expandedFields);
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### `useColumnState`
|
|
76
|
+
|
|
77
|
+
Manages column visibility and relation field expansion.
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
const {
|
|
81
|
+
selectedFields,
|
|
82
|
+
selectedRelations,
|
|
83
|
+
expandedRelationFields,
|
|
84
|
+
toggleField,
|
|
85
|
+
toggleRelation,
|
|
86
|
+
toggleExpandedRelationField,
|
|
87
|
+
selectAll,
|
|
88
|
+
deselectAll,
|
|
89
|
+
} = useColumnState(fields, relations);
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### `useAccessibleTables`
|
|
93
|
+
|
|
94
|
+
Filters tables based on user roles.
|
|
95
|
+
|
|
96
|
+
```tsx
|
|
97
|
+
const tables = useAccessibleTables(tableMetadataMap, userRoles);
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Types
|
|
101
|
+
|
|
102
|
+
### TableMetadata
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
interface TableMetadata {
|
|
106
|
+
name: string;
|
|
107
|
+
pluralForm: string;
|
|
108
|
+
description?: string;
|
|
109
|
+
fields: FieldMetadata[];
|
|
110
|
+
relations?: RelationMetadata[];
|
|
111
|
+
allowedRoles: string[];
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### FieldMetadata
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
interface FieldMetadata {
|
|
119
|
+
name: string;
|
|
120
|
+
type: FieldType;
|
|
121
|
+
nullable: boolean;
|
|
122
|
+
enumValues?: string[];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
type FieldType = "string" | "number" | "boolean" | "uuid" | "datetime" | "date" | "time" | "json" | "enum" | "nested" | "array";
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### RelationMetadata
|
|
129
|
+
|
|
130
|
+
```tsx
|
|
131
|
+
interface RelationMetadata {
|
|
132
|
+
fieldName: string;
|
|
133
|
+
targetTable: string;
|
|
134
|
+
relationType: "manyToOne" | "oneToMany";
|
|
135
|
+
foreignKeyField: string;
|
|
136
|
+
}
|
|
137
|
+
```
|
package/README.md
CHANGED
|
@@ -36,25 +36,14 @@ npm install react react-dom
|
|
|
36
36
|
|
|
37
37
|
### Basic Setup
|
|
38
38
|
|
|
39
|
+
First, set up the metadata generator as described in [Generating Table Metadata](#generating-table-metadata). Then use the generated metadata in your React app:
|
|
40
|
+
|
|
39
41
|
```tsx
|
|
40
42
|
import { DataViewer, SavedViewProvider } from "@izumisy-tailor/tailor-data-viewer/component";
|
|
41
|
-
import type { TableMetadataMap } from "@izumisy-tailor/tailor-data-viewer/generator";
|
|
42
43
|
import "@izumisy-tailor/tailor-data-viewer/styles/theme.css";
|
|
43
44
|
|
|
44
|
-
//
|
|
45
|
-
|
|
46
|
-
user: {
|
|
47
|
-
name: "user",
|
|
48
|
-
pluralForm: "users",
|
|
49
|
-
fields: [
|
|
50
|
-
{ name: "id", type: "uuid", nullable: false },
|
|
51
|
-
{ name: "name", type: "string", nullable: false },
|
|
52
|
-
{ name: "email", type: "string", nullable: false },
|
|
53
|
-
],
|
|
54
|
-
allowedRoles: ["admin", "viewer"],
|
|
55
|
-
},
|
|
56
|
-
// ... more tables
|
|
57
|
-
};
|
|
45
|
+
// Import the generated metadata from your Tailor Platform SDK project
|
|
46
|
+
import { tableMetadata } from "./generated/table-metadata";
|
|
58
47
|
|
|
59
48
|
function App() {
|
|
60
49
|
return (
|
|
@@ -85,150 +74,44 @@ import {
|
|
|
85
74
|
|
|
86
75
|
### Generating Table Metadata
|
|
87
76
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
```tsx
|
|
91
|
-
import { generateTableMetadata } from "@izumisy-tailor/tailor-data-viewer/generator";
|
|
92
|
-
|
|
93
|
-
// From your GraphQL schema types
|
|
94
|
-
const metadata = generateTableMetadata(schemaTypes);
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
## API Reference
|
|
98
|
-
|
|
99
|
-
### `<DataViewer />`
|
|
100
|
-
|
|
101
|
-
Main component with tab-based interface.
|
|
102
|
-
|
|
103
|
-
```tsx
|
|
104
|
-
interface DataViewerProps {
|
|
105
|
-
/** Map of table name to metadata */
|
|
106
|
-
tableMetadata: TableMetadataMap;
|
|
107
|
-
/** User's roles for access control */
|
|
108
|
-
userRoles: string[];
|
|
109
|
-
/** GraphQL endpoint URI */
|
|
110
|
-
appUri: string;
|
|
111
|
-
/** Optional initial view ID to load */
|
|
112
|
-
initialViewId?: string;
|
|
113
|
-
}
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
### `<SavedViewProvider />`
|
|
117
|
-
|
|
118
|
-
Context provider for view persistence (localStorage-based).
|
|
119
|
-
|
|
120
|
-
```tsx
|
|
121
|
-
<SavedViewProvider>
|
|
122
|
-
<DataViewer {...props} />
|
|
123
|
-
</SavedViewProvider>
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
### `<DataTable />`
|
|
127
|
-
|
|
128
|
-
Core data table component with sortable columns and expandable relations.
|
|
129
|
-
|
|
130
|
-
```tsx
|
|
131
|
-
interface DataTableProps {
|
|
132
|
-
data: Record<string, unknown>[];
|
|
133
|
-
fields: FieldMetadata[];
|
|
134
|
-
selectedFields: string[];
|
|
135
|
-
sortState: SortState | null;
|
|
136
|
-
onSort: (field: string) => void;
|
|
137
|
-
loading?: boolean;
|
|
138
|
-
tableMetadata?: TableMetadata;
|
|
139
|
-
tableMetadataMap?: TableMetadataMap;
|
|
140
|
-
appUri?: string;
|
|
141
|
-
selectedRelations?: string[];
|
|
142
|
-
onOpenAsSheet?: (tableName: string, filterField: string, filterValue: string) => void;
|
|
143
|
-
onOpenSingleRecordAsSheet?: (tableName: string, recordId: string) => void;
|
|
144
|
-
expandedRelationFields?: ExpandedRelationFields;
|
|
145
|
-
}
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
### Hooks
|
|
77
|
+
This library includes a custom generator for [Tailor Platform SDK](https://www.npmjs.com/package/@tailor-platform/sdk) that automatically generates table metadata from your TailorDB schema.
|
|
149
78
|
|
|
150
|
-
####
|
|
79
|
+
#### Setup
|
|
151
80
|
|
|
152
|
-
|
|
81
|
+
1. Install the generator package in your Tailor Platform SDK project:
|
|
153
82
|
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
data,
|
|
157
|
-
loading,
|
|
158
|
-
error,
|
|
159
|
-
pagination,
|
|
160
|
-
sortState,
|
|
161
|
-
setSort,
|
|
162
|
-
nextPage,
|
|
163
|
-
previousPage,
|
|
164
|
-
resetPagination,
|
|
165
|
-
refetch,
|
|
166
|
-
} = useTableData(appUri, table, selectedFields, selectedRelations, filters, metadataMap, expandedFields);
|
|
83
|
+
```bash
|
|
84
|
+
npm install @izumisy-tailor/tailor-data-viewer
|
|
167
85
|
```
|
|
168
86
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
Manages column visibility and relation field expansion.
|
|
87
|
+
2. Configure the generator in your `tailor.config.ts`:
|
|
172
88
|
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
selectedRelations,
|
|
177
|
-
expandedRelationFields,
|
|
178
|
-
toggleField,
|
|
179
|
-
toggleRelation,
|
|
180
|
-
toggleExpandedRelationField,
|
|
181
|
-
selectAll,
|
|
182
|
-
deselectAll,
|
|
183
|
-
} = useColumnState(fields, relations);
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
#### `useAccessibleTables`
|
|
89
|
+
```typescript
|
|
90
|
+
import { defineConfig, defineGenerators } from "@tailor-platform/sdk";
|
|
91
|
+
import tableMetadataGenerator from "@izumisy-tailor/tailor-data-viewer/generator";
|
|
187
92
|
|
|
188
|
-
|
|
93
|
+
export const generators = defineGenerators(
|
|
94
|
+
tableMetadataGenerator,
|
|
95
|
+
// ... other generators
|
|
96
|
+
);
|
|
189
97
|
|
|
190
|
-
|
|
191
|
-
|
|
98
|
+
export default defineConfig({
|
|
99
|
+
name: "my-app",
|
|
100
|
+
// ... your config
|
|
101
|
+
});
|
|
192
102
|
```
|
|
193
103
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
### TableMetadata
|
|
104
|
+
3. Run the generator:
|
|
197
105
|
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
name: string;
|
|
201
|
-
pluralForm: string;
|
|
202
|
-
description?: string;
|
|
203
|
-
fields: FieldMetadata[];
|
|
204
|
-
relations?: RelationMetadata[];
|
|
205
|
-
allowedRoles: string[];
|
|
206
|
-
}
|
|
106
|
+
```bash
|
|
107
|
+
tailor-sdk generate
|
|
207
108
|
```
|
|
208
109
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
```tsx
|
|
212
|
-
interface FieldMetadata {
|
|
213
|
-
name: string;
|
|
214
|
-
type: FieldType;
|
|
215
|
-
nullable: boolean;
|
|
216
|
-
enumValues?: string[];
|
|
217
|
-
}
|
|
110
|
+
This will generate a `src/generated/table-metadata.ts` file containing type-safe metadata for all your TailorDB types, including fields, relations, and role-based access control settings.
|
|
218
111
|
|
|
219
|
-
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
### RelationMetadata
|
|
112
|
+
## API Reference
|
|
223
113
|
|
|
224
|
-
|
|
225
|
-
interface RelationMetadata {
|
|
226
|
-
fieldName: string;
|
|
227
|
-
targetTable: string;
|
|
228
|
-
relationType: "manyToOne" | "oneToMany";
|
|
229
|
-
foreignKeyField: string;
|
|
230
|
-
}
|
|
231
|
-
```
|
|
114
|
+
See [API.md](API.md) for detailed API documentation including components, hooks, and types.
|
|
232
115
|
|
|
233
116
|
## Styling
|
|
234
117
|
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
//#region src/generator/metadata-generator.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Field type mapping for Data View
|
|
4
|
+
*/
|
|
5
|
+
type FieldType = "string" | "number" | "boolean" | "uuid" | "datetime" | "date" | "time" | "enum" | "array" | "nested";
|
|
6
|
+
/**
|
|
7
|
+
* Metadata for a single field
|
|
8
|
+
*/
|
|
9
|
+
interface FieldMetadata {
|
|
10
|
+
name: string;
|
|
11
|
+
type: FieldType;
|
|
12
|
+
required: boolean;
|
|
13
|
+
enumValues?: readonly string[];
|
|
14
|
+
arrayItemType?: FieldType;
|
|
15
|
+
description?: string;
|
|
16
|
+
/** manyToOne relation info (if this field is a foreign key) */
|
|
17
|
+
relation?: {
|
|
18
|
+
/** GraphQL field name for the related object (e.g., "task") */fieldName: string; /** Target table name in camelCase (e.g., "task") */
|
|
19
|
+
targetTable: string;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Metadata for a relation
|
|
24
|
+
*/
|
|
25
|
+
interface RelationMetadata {
|
|
26
|
+
/** GraphQL field name (e.g., "task" for manyToOne, "taskAssignments" for oneToMany) */
|
|
27
|
+
fieldName: string;
|
|
28
|
+
/** Target table name in camelCase (e.g., "task", "taskAssignment") */
|
|
29
|
+
targetTable: string;
|
|
30
|
+
/** Relation type */
|
|
31
|
+
relationType: "manyToOne" | "oneToMany";
|
|
32
|
+
/** For manyToOne: the FK field name (e.g., "taskId"). For oneToMany: the FK field on the child table */
|
|
33
|
+
foreignKeyField: string;
|
|
34
|
+
/** For manyToOne: the backward field name on the target type (used to generate oneToMany relation) */
|
|
35
|
+
backwardFieldName?: string;
|
|
36
|
+
/** True if this is a oneToOne relation (no inverse oneToMany should be generated) */
|
|
37
|
+
isOneToOne?: boolean;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Metadata for a single table
|
|
41
|
+
*/
|
|
42
|
+
interface TableMetadata {
|
|
43
|
+
name: string;
|
|
44
|
+
pluralForm: string;
|
|
45
|
+
description?: string;
|
|
46
|
+
readAllowedRoles: string[];
|
|
47
|
+
fields: FieldMetadata[];
|
|
48
|
+
/** Relations (manyToOne and oneToMany) */
|
|
49
|
+
relations?: RelationMetadata[];
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Map of all tables
|
|
53
|
+
*/
|
|
54
|
+
type TableMetadataMap = Record<string, TableMetadata>;
|
|
55
|
+
/**
|
|
56
|
+
* Intermediate type for processed table data
|
|
57
|
+
*/
|
|
58
|
+
interface ProcessedTable {
|
|
59
|
+
name: string;
|
|
60
|
+
pluralForm: string;
|
|
61
|
+
originalName: string;
|
|
62
|
+
description?: string;
|
|
63
|
+
readAllowedRoles: string[];
|
|
64
|
+
fields: FieldMetadata[];
|
|
65
|
+
relations: RelationMetadata[];
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Parsed field type from TailorDB
|
|
69
|
+
*/
|
|
70
|
+
interface ParsedFieldConfig {
|
|
71
|
+
type: string;
|
|
72
|
+
required?: boolean;
|
|
73
|
+
description?: string;
|
|
74
|
+
allowedValues?: ({
|
|
75
|
+
value: string;
|
|
76
|
+
} | string)[];
|
|
77
|
+
array?: boolean;
|
|
78
|
+
/** Raw relation configuration from TailorDB SDK */
|
|
79
|
+
rawRelation?: {
|
|
80
|
+
type: "manyToOne" | "n-1" | "oneToOne" | "1-1" | "keyOnly";
|
|
81
|
+
toward: {
|
|
82
|
+
/** Target type name (PascalCase) */type: string; /** GraphQL field alias for the relation */
|
|
83
|
+
as?: string;
|
|
84
|
+
}; /** Backward relation field name on the target type */
|
|
85
|
+
backward?: string;
|
|
86
|
+
};
|
|
87
|
+
/** Foreign key info */
|
|
88
|
+
foreignKey?: boolean;
|
|
89
|
+
foreignKeyType?: string;
|
|
90
|
+
foreignKeyField?: string;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Parsed field from TailorDB
|
|
94
|
+
*/
|
|
95
|
+
interface ParsedField {
|
|
96
|
+
name: string;
|
|
97
|
+
config: ParsedFieldConfig;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Parsed TailorDB type
|
|
101
|
+
*/
|
|
102
|
+
interface ParsedTailorDBType {
|
|
103
|
+
name: string;
|
|
104
|
+
pluralForm: string;
|
|
105
|
+
description?: string;
|
|
106
|
+
fields: Record<string, ParsedField>;
|
|
107
|
+
permissions: {
|
|
108
|
+
gql?: readonly GqlPermissionPolicy[];
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* GQL permission policy
|
|
113
|
+
*/
|
|
114
|
+
interface GqlPermissionPolicy {
|
|
115
|
+
conditions: readonly unknown[];
|
|
116
|
+
actions: readonly ["all"] | readonly string[];
|
|
117
|
+
permit: "allow" | "deny";
|
|
118
|
+
description?: string;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Generator input structure
|
|
122
|
+
*/
|
|
123
|
+
interface GeneratorInput {
|
|
124
|
+
tailordb: {
|
|
125
|
+
types: ProcessedTable[];
|
|
126
|
+
}[];
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Generator result file
|
|
130
|
+
*/
|
|
131
|
+
interface GeneratorResultFile {
|
|
132
|
+
path: string;
|
|
133
|
+
content: string;
|
|
134
|
+
skipIfExists?: boolean;
|
|
135
|
+
executable?: boolean;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Generator result
|
|
139
|
+
*/
|
|
140
|
+
interface GeneratorResult {
|
|
141
|
+
files: GeneratorResultFile[];
|
|
142
|
+
errors?: string[];
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Custom generator that extracts table metadata for Data View
|
|
146
|
+
*/
|
|
147
|
+
declare const tableMetadataGenerator: {
|
|
148
|
+
id: string;
|
|
149
|
+
description: string;
|
|
150
|
+
dependencies: ("tailordb" | "resolver" | "executor")[];
|
|
151
|
+
processType({
|
|
152
|
+
type
|
|
153
|
+
}: {
|
|
154
|
+
type: ParsedTailorDBType;
|
|
155
|
+
}): ProcessedTable;
|
|
156
|
+
processResolver(): null;
|
|
157
|
+
processExecutor(): null;
|
|
158
|
+
processTailorDBNamespace({
|
|
159
|
+
types
|
|
160
|
+
}: {
|
|
161
|
+
types: Record<string, ProcessedTable>;
|
|
162
|
+
}): ProcessedTable[];
|
|
163
|
+
aggregate({
|
|
164
|
+
input
|
|
165
|
+
}: {
|
|
166
|
+
input: GeneratorInput;
|
|
167
|
+
}): GeneratorResult;
|
|
168
|
+
};
|
|
169
|
+
//#endregion
|
|
170
|
+
export { FieldMetadata, FieldType, RelationMetadata, TableMetadata, TableMetadataMap, tableMetadataGenerator as default, tableMetadataGenerator };
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
//#region src/generator/metadata-generator.ts
|
|
2
|
+
/**
|
|
3
|
+
* Map TailorDB field type to FieldType
|
|
4
|
+
*/
|
|
5
|
+
function mapFieldType(tailorType, isArray) {
|
|
6
|
+
const mappedType = {
|
|
7
|
+
string: "string",
|
|
8
|
+
integer: "number",
|
|
9
|
+
float: "number",
|
|
10
|
+
boolean: "boolean",
|
|
11
|
+
uuid: "uuid",
|
|
12
|
+
datetime: "datetime",
|
|
13
|
+
date: "date",
|
|
14
|
+
time: "time",
|
|
15
|
+
enum: "enum",
|
|
16
|
+
nested: "nested"
|
|
17
|
+
}[tailorType] ?? "string";
|
|
18
|
+
if (isArray) return {
|
|
19
|
+
type: "array",
|
|
20
|
+
arrayItemType: mappedType
|
|
21
|
+
};
|
|
22
|
+
return { type: mappedType };
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Convert PascalCase to camelCase
|
|
26
|
+
*/
|
|
27
|
+
function toCamelCase(str) {
|
|
28
|
+
return str.charAt(0).toLowerCase() + str.slice(1);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Extract allowed roles from gql permission policies
|
|
32
|
+
* Only extracts roles from 'read' action policies with 'allow' permit
|
|
33
|
+
*/
|
|
34
|
+
function extractReadAllowedRoles(gqlPermission) {
|
|
35
|
+
if (!gqlPermission) return [];
|
|
36
|
+
const roles = /* @__PURE__ */ new Set();
|
|
37
|
+
for (const policy of gqlPermission) {
|
|
38
|
+
if (policy.permit !== "allow") continue;
|
|
39
|
+
const actions = policy.actions;
|
|
40
|
+
if (!actions.includes("all") && !actions.includes("read")) continue;
|
|
41
|
+
for (const condition of policy.conditions) {
|
|
42
|
+
if (!Array.isArray(condition) || condition.length < 3) continue;
|
|
43
|
+
const [left, operator, right] = condition;
|
|
44
|
+
if (typeof left === "string" && operator === "in" && typeof right === "object" && right !== null && "user" in right && right.user === "roles") roles.add(left);
|
|
45
|
+
if (typeof right === "string" && operator === "in" && typeof left === "object" && left !== null && "user" in left && left.user === "roles") roles.add(right);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return Array.from(roles);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Custom generator that extracts table metadata for Data View
|
|
52
|
+
*/
|
|
53
|
+
const tableMetadataGenerator = {
|
|
54
|
+
id: "table-metadata",
|
|
55
|
+
description: "Generates table metadata for Data View including field definitions and read permissions",
|
|
56
|
+
dependencies: ["tailordb"],
|
|
57
|
+
processType({ type }) {
|
|
58
|
+
const fields = [];
|
|
59
|
+
const relations = [];
|
|
60
|
+
for (const [fieldName, field] of Object.entries(type.fields)) {
|
|
61
|
+
const config = field.config;
|
|
62
|
+
const { type: fieldType, arrayItemType } = mapFieldType(config.type, config.array);
|
|
63
|
+
const fieldMetadata = {
|
|
64
|
+
name: fieldName,
|
|
65
|
+
type: fieldType,
|
|
66
|
+
required: config.required ?? false,
|
|
67
|
+
description: config.description
|
|
68
|
+
};
|
|
69
|
+
if (config.allowedValues && config.allowedValues.length > 0) fieldMetadata.enumValues = config.allowedValues.map((v) => typeof v === "string" ? v : v.value);
|
|
70
|
+
if (arrayItemType) fieldMetadata.arrayItemType = arrayItemType;
|
|
71
|
+
const rawRelation = config.rawRelation;
|
|
72
|
+
const isManyToOneRelation = rawRelation && (rawRelation.type === "manyToOne" || rawRelation.type === "n-1" || rawRelation.type === "oneToOne" || rawRelation.type === "1-1") && rawRelation.toward;
|
|
73
|
+
const isOneToOne = rawRelation?.type === "oneToOne" || rawRelation?.type === "1-1";
|
|
74
|
+
if (isManyToOneRelation && rawRelation.toward) {
|
|
75
|
+
const targetTableName = toCamelCase(rawRelation.toward.type);
|
|
76
|
+
const relationFieldName = rawRelation.toward.as ?? toCamelCase(rawRelation.toward.type);
|
|
77
|
+
fieldMetadata.relation = {
|
|
78
|
+
fieldName: relationFieldName,
|
|
79
|
+
targetTable: targetTableName
|
|
80
|
+
};
|
|
81
|
+
relations.push({
|
|
82
|
+
fieldName: relationFieldName,
|
|
83
|
+
targetTable: targetTableName,
|
|
84
|
+
relationType: "manyToOne",
|
|
85
|
+
foreignKeyField: fieldName,
|
|
86
|
+
backwardFieldName: rawRelation.backward,
|
|
87
|
+
isOneToOne
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
fields.push(fieldMetadata);
|
|
91
|
+
}
|
|
92
|
+
const readAllowedRoles = extractReadAllowedRoles(type.permissions.gql);
|
|
93
|
+
return {
|
|
94
|
+
name: toCamelCase(type.name),
|
|
95
|
+
pluralForm: toCamelCase(type.pluralForm),
|
|
96
|
+
originalName: type.name,
|
|
97
|
+
description: type.description,
|
|
98
|
+
readAllowedRoles,
|
|
99
|
+
fields,
|
|
100
|
+
relations
|
|
101
|
+
};
|
|
102
|
+
},
|
|
103
|
+
processResolver() {
|
|
104
|
+
return null;
|
|
105
|
+
},
|
|
106
|
+
processExecutor() {
|
|
107
|
+
return null;
|
|
108
|
+
},
|
|
109
|
+
processTailorDBNamespace({ types }) {
|
|
110
|
+
return Object.values(types);
|
|
111
|
+
},
|
|
112
|
+
aggregate({ input }) {
|
|
113
|
+
const allTables = input.tailordb.flatMap((ns) => ns.types);
|
|
114
|
+
const tableByOriginalName = /* @__PURE__ */ new Map();
|
|
115
|
+
for (const table of allTables) tableByOriginalName.set(table.originalName, table);
|
|
116
|
+
for (const table of allTables) for (const relation of table.relations) if (relation.relationType === "manyToOne") {
|
|
117
|
+
if (relation.isOneToOne) continue;
|
|
118
|
+
const targetTable = tableByOriginalName.get(relation.targetTable.charAt(0).toUpperCase() + relation.targetTable.slice(1));
|
|
119
|
+
if (targetTable) {
|
|
120
|
+
const oneToManyFieldName = relation.backwardFieldName ?? table.pluralForm;
|
|
121
|
+
if (!targetTable.relations.some((r) => r.relationType === "oneToMany" && r.fieldName === oneToManyFieldName)) targetTable.relations.push({
|
|
122
|
+
fieldName: oneToManyFieldName,
|
|
123
|
+
targetTable: table.name,
|
|
124
|
+
relationType: "oneToMany",
|
|
125
|
+
foreignKeyField: relation.foreignKeyField
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const metadataMap = {};
|
|
130
|
+
for (const table of allTables) {
|
|
131
|
+
const { originalName, ...tableWithoutOriginalName } = table;
|
|
132
|
+
metadataMap[table.name] = tableWithoutOriginalName;
|
|
133
|
+
}
|
|
134
|
+
return { files: [{
|
|
135
|
+
path: "src/generated/table-metadata.ts",
|
|
136
|
+
content: `// This file is auto-generated by table-metadata-generator
|
|
137
|
+
// Do not edit manually
|
|
138
|
+
|
|
139
|
+
import type {
|
|
140
|
+
FieldType,
|
|
141
|
+
FieldMetadata,
|
|
142
|
+
RelationMetadata,
|
|
143
|
+
TableMetadata,
|
|
144
|
+
TableMetadataMap,
|
|
145
|
+
} from "../../generator/table-metadata-generator";
|
|
146
|
+
|
|
147
|
+
export type { FieldType, FieldMetadata, RelationMetadata, TableMetadata, TableMetadataMap };
|
|
148
|
+
|
|
149
|
+
export const tableMetadata: TableMetadataMap = ${JSON.stringify(metadataMap, null, 2)} as const;
|
|
150
|
+
|
|
151
|
+
export const tableNames = ${JSON.stringify(Object.keys(metadataMap), null, 2)} as const;
|
|
152
|
+
|
|
153
|
+
export type TableName = (typeof tableNames)[number];
|
|
154
|
+
`
|
|
155
|
+
}] };
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
var metadata_generator_default = tableMetadataGenerator;
|
|
159
|
+
|
|
160
|
+
//#endregion
|
|
161
|
+
export { metadata_generator_default as default, tableMetadataGenerator };
|
package/package.json
CHANGED
|
@@ -1,25 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@izumisy-tailor/tailor-data-viewer",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.3",
|
|
5
|
+
"type": "module",
|
|
5
6
|
"description": "Flexible data viewer component for Tailor Platform",
|
|
6
7
|
"files": [
|
|
7
|
-
"src"
|
|
8
|
+
"src",
|
|
9
|
+
"dist",
|
|
10
|
+
"API.md"
|
|
8
11
|
],
|
|
9
12
|
"exports": {
|
|
10
13
|
"./component": {
|
|
11
14
|
"default": "./src/component/index.ts"
|
|
12
15
|
},
|
|
13
16
|
"./generator": {
|
|
14
|
-
"
|
|
17
|
+
"types": "./dist/generator/index.d.mts",
|
|
18
|
+
"default": "./dist/generator/index.mjs"
|
|
15
19
|
},
|
|
16
20
|
"./styles": {
|
|
17
21
|
"default": "./src/styles/theme.css"
|
|
18
22
|
}
|
|
19
23
|
},
|
|
20
24
|
"dependencies": {
|
|
21
|
-
"graphql-request": "^6.1.0",
|
|
22
|
-
"lucide-react": "^0.468.0",
|
|
23
25
|
"@radix-ui/react-checkbox": "^1.1.4",
|
|
24
26
|
"@radix-ui/react-collapsible": "^1.1.3",
|
|
25
27
|
"@radix-ui/react-dialog": "^1.1.6",
|
|
@@ -29,6 +31,8 @@
|
|
|
29
31
|
"@radix-ui/react-slot": "^1.1.2",
|
|
30
32
|
"class-variance-authority": "^0.7.1",
|
|
31
33
|
"clsx": "^2.1.1",
|
|
34
|
+
"graphql-request": "^6.1.0",
|
|
35
|
+
"lucide-react": "^0.468.0",
|
|
32
36
|
"tailwind-merge": "^2.6.0"
|
|
33
37
|
},
|
|
34
38
|
"peerDependencies": {
|
|
@@ -36,12 +40,15 @@
|
|
|
36
40
|
"react-dom": "^18.0.0 || ^19.0.0"
|
|
37
41
|
},
|
|
38
42
|
"devDependencies": {
|
|
43
|
+
"@types/react": "^19.0.0",
|
|
44
|
+
"@types/react-dom": "^19.0.0",
|
|
39
45
|
"react": "^19.0.0",
|
|
40
46
|
"react-dom": "^19.0.0",
|
|
41
|
-
"
|
|
42
|
-
"
|
|
47
|
+
"tsdown": "^0.20.1",
|
|
48
|
+
"typescript": "^5.9.3"
|
|
43
49
|
},
|
|
44
50
|
"scripts": {
|
|
51
|
+
"build": "tsdown",
|
|
45
52
|
"type-check": "tsc -b"
|
|
46
53
|
}
|
|
47
54
|
}
|