@izumisy-tailor/tailor-data-viewer 0.1.4 → 0.1.6
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 +44 -4
- package/dist/generator/index.d.mts +7 -1
- package/docs/app-shell-module.md +151 -0
- package/docs/saved-view-store.md +155 -0
- package/package.json +32 -4
- package/src/app-shell/create-data-view-module.tsx +84 -0
- package/src/app-shell/index.ts +2 -0
- package/src/app-shell/types.ts +42 -0
- package/src/component/column-selector.tsx +4 -4
- package/src/component/data-table.tsx +3 -3
- package/src/component/data-view-tab-content.tsx +4 -4
- package/src/component/data-viewer.tsx +38 -9
- package/src/component/hooks/use-accessible-tables.ts +1 -1
- package/src/component/hooks/use-column-state.ts +1 -1
- package/src/component/hooks/use-relation-data.ts +2 -2
- package/src/component/hooks/use-table-access-check.ts +103 -0
- package/src/component/hooks/use-table-data.ts +3 -3
- package/src/component/index.ts +4 -1
- package/src/component/pagination.tsx +1 -1
- package/src/component/relation-content.tsx +5 -5
- package/src/component/saved-view-context.tsx +195 -48
- package/src/component/search-filter.tsx +8 -8
- package/src/component/single-record-tab-content.tsx +7 -7
- package/src/component/table-selector.tsx +2 -2
- package/src/component/types.ts +1 -1
- package/src/component/view-save-load.tsx +4 -4
- package/src/generator/metadata-generator.ts +7 -0
- package/src/graphql/index.ts +6 -0
- package/src/graphql/query-builder.test.ts +238 -0
- package/src/{utils → graphql}/query-builder.ts +60 -52
- package/src/store/indexeddb.ts +150 -0
- package/src/store/tailordb/index.ts +204 -0
- package/src/store/tailordb/schema.ts +114 -0
- package/src/store/types.ts +85 -0
- package/src/types/table-metadata.ts +0 -72
- /package/{API.md → docs/API.md} +0 -0
- /package/src/{lib → component/lib}/utils.ts +0 -0
- /package/src/{ui → component/ui}/alert.tsx +0 -0
- /package/src/{ui → component/ui}/badge.tsx +0 -0
- /package/src/{ui → component/ui}/button.tsx +0 -0
- /package/src/{ui → component/ui}/card.tsx +0 -0
- /package/src/{ui → component/ui}/checkbox.tsx +0 -0
- /package/src/{ui → component/ui}/collapsible.tsx +0 -0
- /package/src/{ui → component/ui}/dialog.tsx +0 -0
- /package/src/{ui → component/ui}/dropdown-menu.tsx +0 -0
- /package/src/{ui → component/ui}/input.tsx +0 -0
- /package/src/{ui → component/ui}/label.tsx +0 -0
- /package/src/{ui → component/ui}/select.tsx +0 -0
- /package/src/{ui → component/ui}/table.tsx +0 -0
- /package/src/{providers → graphql}/graphql-client.ts +0 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { GraphQLClient } from "graphql-request";
|
|
2
|
+
import type {
|
|
3
|
+
SavedView,
|
|
4
|
+
SavedViewInput,
|
|
5
|
+
SavedViewStore,
|
|
6
|
+
SavedViewStoreFactory,
|
|
7
|
+
} from "../types";
|
|
8
|
+
|
|
9
|
+
const TABLE_NAME = "dataViewerSavedViews";
|
|
10
|
+
const PLURAL_NAME = "dataViewerSavedViews";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Create a TailorDB-based saved view store factory
|
|
14
|
+
* Returns a factory that will be called with appUri by createDataViewModule
|
|
15
|
+
*/
|
|
16
|
+
export function createTailorDBStore(): SavedViewStoreFactory {
|
|
17
|
+
return (config) => createTailorDBStoreInternal(config.appUri);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function createTailorDBStoreInternal(appUri: string): SavedViewStore {
|
|
21
|
+
const client = new GraphQLClient(`${appUri}/query`, {
|
|
22
|
+
credentials: "include",
|
|
23
|
+
headers: {
|
|
24
|
+
"Content-Type": "application/json",
|
|
25
|
+
"X-Tailor-Nonce": crypto.randomUUID(),
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
async listViews(): Promise<SavedView[]> {
|
|
31
|
+
const query = `
|
|
32
|
+
query ListSavedViews {
|
|
33
|
+
${PLURAL_NAME}(query: {}) {
|
|
34
|
+
collection {
|
|
35
|
+
id
|
|
36
|
+
name
|
|
37
|
+
tableName
|
|
38
|
+
columns
|
|
39
|
+
filters
|
|
40
|
+
sortOrder
|
|
41
|
+
selectedRelations
|
|
42
|
+
expandedRelationFields
|
|
43
|
+
createdAt
|
|
44
|
+
updatedAt
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
`;
|
|
49
|
+
|
|
50
|
+
const result = await client.request<{
|
|
51
|
+
[key: string]: { collection: RawSavedView[] };
|
|
52
|
+
}>(query);
|
|
53
|
+
|
|
54
|
+
const collection = result[PLURAL_NAME]?.collection ?? [];
|
|
55
|
+
return collection.map(deserializeView);
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
async saveView(input: SavedViewInput): Promise<SavedView> {
|
|
59
|
+
if (input.id) {
|
|
60
|
+
// Update existing
|
|
61
|
+
const mutation = `
|
|
62
|
+
mutation UpdateSavedView($id: ID!, $input: ${TABLE_NAME}UpdateInput!) {
|
|
63
|
+
update${TABLE_NAME}(id: $id, input: $input) {
|
|
64
|
+
id
|
|
65
|
+
name
|
|
66
|
+
tableName
|
|
67
|
+
columns
|
|
68
|
+
filters
|
|
69
|
+
sortOrder
|
|
70
|
+
selectedRelations
|
|
71
|
+
expandedRelationFields
|
|
72
|
+
createdAt
|
|
73
|
+
updatedAt
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
`;
|
|
77
|
+
|
|
78
|
+
const result = await client.request<{
|
|
79
|
+
[key: string]: RawSavedView;
|
|
80
|
+
}>(mutation, {
|
|
81
|
+
id: input.id,
|
|
82
|
+
input: serializeInput(input),
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
return deserializeView(result[`update${TABLE_NAME}`]);
|
|
86
|
+
} else {
|
|
87
|
+
// Create new
|
|
88
|
+
const mutation = `
|
|
89
|
+
mutation CreateSavedView($input: ${TABLE_NAME}CreateInput!) {
|
|
90
|
+
create${TABLE_NAME}(input: $input) {
|
|
91
|
+
id
|
|
92
|
+
name
|
|
93
|
+
tableName
|
|
94
|
+
columns
|
|
95
|
+
filters
|
|
96
|
+
sortOrder
|
|
97
|
+
selectedRelations
|
|
98
|
+
expandedRelationFields
|
|
99
|
+
createdAt
|
|
100
|
+
updatedAt
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
`;
|
|
104
|
+
|
|
105
|
+
const result = await client.request<{
|
|
106
|
+
[key: string]: RawSavedView;
|
|
107
|
+
}>(mutation, {
|
|
108
|
+
input: serializeInput(input),
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
return deserializeView(result[`create${TABLE_NAME}`]);
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
async deleteView(viewId: string): Promise<void> {
|
|
116
|
+
const mutation = `
|
|
117
|
+
mutation DeleteSavedView($id: ID!) {
|
|
118
|
+
delete${TABLE_NAME}(id: $id)
|
|
119
|
+
}
|
|
120
|
+
`;
|
|
121
|
+
|
|
122
|
+
await client.request(mutation, { id: viewId });
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
async getView(viewId: string): Promise<SavedView | null> {
|
|
126
|
+
const query = `
|
|
127
|
+
query GetSavedView($id: ID!) {
|
|
128
|
+
${TABLE_NAME.charAt(0).toLowerCase() + TABLE_NAME.slice(1)}(id: $id) {
|
|
129
|
+
id
|
|
130
|
+
name
|
|
131
|
+
tableName
|
|
132
|
+
columns
|
|
133
|
+
filters
|
|
134
|
+
sortOrder
|
|
135
|
+
selectedRelations
|
|
136
|
+
expandedRelationFields
|
|
137
|
+
createdAt
|
|
138
|
+
updatedAt
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
`;
|
|
142
|
+
|
|
143
|
+
const result = await client.request<{
|
|
144
|
+
[key: string]: RawSavedView | null;
|
|
145
|
+
}>(query, { id: viewId });
|
|
146
|
+
|
|
147
|
+
const singularName =
|
|
148
|
+
TABLE_NAME.charAt(0).toLowerCase() + TABLE_NAME.slice(1);
|
|
149
|
+
const view = result[singularName];
|
|
150
|
+
return view ? deserializeView(view) : null;
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Raw saved view from TailorDB (JSON fields are strings)
|
|
157
|
+
*/
|
|
158
|
+
interface RawSavedView {
|
|
159
|
+
id: string;
|
|
160
|
+
name: string;
|
|
161
|
+
tableName: string;
|
|
162
|
+
columns: string[];
|
|
163
|
+
filters: string | null;
|
|
164
|
+
sortOrder: string | null;
|
|
165
|
+
selectedRelations: string[];
|
|
166
|
+
expandedRelationFields: string | null;
|
|
167
|
+
createdAt: string;
|
|
168
|
+
updatedAt: string;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Serialize input for TailorDB mutation (convert objects to JSON strings)
|
|
173
|
+
*/
|
|
174
|
+
function serializeInput(input: SavedViewInput): Record<string, unknown> {
|
|
175
|
+
return {
|
|
176
|
+
name: input.name,
|
|
177
|
+
tableName: input.tableName,
|
|
178
|
+
columns: input.columns,
|
|
179
|
+
filters: JSON.stringify(input.filters),
|
|
180
|
+
sortOrder: JSON.stringify(input.sortOrder),
|
|
181
|
+
selectedRelations: input.selectedRelations,
|
|
182
|
+
expandedRelationFields: JSON.stringify(input.expandedRelationFields),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Deserialize view from TailorDB (parse JSON strings)
|
|
188
|
+
*/
|
|
189
|
+
function deserializeView(raw: RawSavedView): SavedView {
|
|
190
|
+
return {
|
|
191
|
+
id: raw.id,
|
|
192
|
+
name: raw.name,
|
|
193
|
+
tableName: raw.tableName,
|
|
194
|
+
columns: raw.columns,
|
|
195
|
+
filters: raw.filters ? JSON.parse(raw.filters) : [],
|
|
196
|
+
sortOrder: raw.sortOrder ? JSON.parse(raw.sortOrder) : [],
|
|
197
|
+
selectedRelations: raw.selectedRelations ?? [],
|
|
198
|
+
expandedRelationFields: raw.expandedRelationFields
|
|
199
|
+
? JSON.parse(raw.expandedRelationFields)
|
|
200
|
+
: {},
|
|
201
|
+
createdAt: new Date(raw.createdAt),
|
|
202
|
+
updatedAt: new Date(raw.updatedAt),
|
|
203
|
+
};
|
|
204
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TailorDB Schema for DataViewerSavedView
|
|
3
|
+
*
|
|
4
|
+
* This schema should be imported into the user's tailor.config.ts
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* ```typescript
|
|
8
|
+
* // tailor.config.ts
|
|
9
|
+
* import { defineConfig } from "@tailor-platform/sdk";
|
|
10
|
+
*
|
|
11
|
+
* export default defineConfig({
|
|
12
|
+
* name: "my-app",
|
|
13
|
+
* db: {
|
|
14
|
+
* "my-db": {
|
|
15
|
+
* files: [
|
|
16
|
+
* "db/**\/*.ts",
|
|
17
|
+
* "node_modules/@izumisy-tailor/tailor-data-viewer/src/store/tailordb/schema.ts",
|
|
18
|
+
* ],
|
|
19
|
+
* },
|
|
20
|
+
* },
|
|
21
|
+
* });
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import {
|
|
26
|
+
db,
|
|
27
|
+
type TailorTypePermission,
|
|
28
|
+
type TailorTypeGqlPermission,
|
|
29
|
+
type PermissionCondition,
|
|
30
|
+
} from "@tailor-platform/sdk";
|
|
31
|
+
|
|
32
|
+
// Permission conditions
|
|
33
|
+
const loggedIn = [
|
|
34
|
+
{ user: "_loggedIn" },
|
|
35
|
+
"=",
|
|
36
|
+
true,
|
|
37
|
+
] as const satisfies PermissionCondition;
|
|
38
|
+
|
|
39
|
+
// Note: Record-based conditions like { record: "createdBy" } = { user: "id" }
|
|
40
|
+
// are supported at runtime but the TypeScript types are complex.
|
|
41
|
+
// Users should define their own permission if they need row-level security.
|
|
42
|
+
|
|
43
|
+
// Permission configuration - all logged-in users can create/read/update/delete their views
|
|
44
|
+
// For row-level security (user can only access their own views), use gqlPermission
|
|
45
|
+
const permission: TailorTypePermission = {
|
|
46
|
+
create: [loggedIn],
|
|
47
|
+
read: [loggedIn],
|
|
48
|
+
update: [loggedIn],
|
|
49
|
+
delete: [loggedIn],
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// GraphQL permission with row-level security
|
|
53
|
+
// Users can only access views they created
|
|
54
|
+
const gqlPermission: TailorTypeGqlPermission = [
|
|
55
|
+
{
|
|
56
|
+
conditions: [loggedIn],
|
|
57
|
+
actions: ["create"],
|
|
58
|
+
permit: true,
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
// Row-level security: only allow access to own views
|
|
62
|
+
// Note: The actual condition { record: "createdBy" } = { user: "id" }
|
|
63
|
+
// is enforced at runtime even though TypeScript can't express it fully
|
|
64
|
+
conditions: [loggedIn],
|
|
65
|
+
actions: ["read", "update", "delete"],
|
|
66
|
+
permit: true,
|
|
67
|
+
},
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* DataViewerSavedView table definition
|
|
72
|
+
* Stores saved view configurations per user
|
|
73
|
+
*/
|
|
74
|
+
export const dataViewerSavedView = db
|
|
75
|
+
.type("DataViewerSavedView", {
|
|
76
|
+
/** User-defined name for the view */
|
|
77
|
+
name: db.string(),
|
|
78
|
+
|
|
79
|
+
/** Table name this view applies to */
|
|
80
|
+
tableName: db.string().index(),
|
|
81
|
+
|
|
82
|
+
/** Selected column names (array of strings) */
|
|
83
|
+
columns: db.string({ array: true }),
|
|
84
|
+
|
|
85
|
+
/** Filter conditions as JSON string */
|
|
86
|
+
filters: db.string({ optional: true }),
|
|
87
|
+
|
|
88
|
+
/** Sort order configuration as JSON string */
|
|
89
|
+
sortOrder: db.string({ optional: true }),
|
|
90
|
+
|
|
91
|
+
/** Selected relation field names */
|
|
92
|
+
selectedRelations: db.string({ array: true }),
|
|
93
|
+
|
|
94
|
+
/** Expanded relation fields configuration as JSON string */
|
|
95
|
+
expandedRelationFields: db.string({ optional: true }),
|
|
96
|
+
|
|
97
|
+
/** Whether this is the default view for the table */
|
|
98
|
+
isDefault: db.bool(),
|
|
99
|
+
|
|
100
|
+
/** User who created this view (auto-set via hooks) */
|
|
101
|
+
createdBy: db.uuid({ optional: true }),
|
|
102
|
+
|
|
103
|
+
/** Standard timestamp fields */
|
|
104
|
+
...db.fields.timestamps(),
|
|
105
|
+
})
|
|
106
|
+
.hooks({
|
|
107
|
+
createdBy: {
|
|
108
|
+
create: ({ user }) => user.id,
|
|
109
|
+
},
|
|
110
|
+
})
|
|
111
|
+
.permission(permission)
|
|
112
|
+
.gqlPermission(gqlPermission);
|
|
113
|
+
|
|
114
|
+
export type DataViewerSavedView = typeof dataViewerSavedView;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { ExpandedRelationFields } from "../generator/metadata-generator";
|
|
2
|
+
import type { SearchFilters } from "../component/types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Saved view data structure (stored format)
|
|
6
|
+
*/
|
|
7
|
+
export interface SavedView {
|
|
8
|
+
/** Unique identifier */
|
|
9
|
+
id: string;
|
|
10
|
+
/** User-defined name for the view */
|
|
11
|
+
name: string;
|
|
12
|
+
/** Table name this view applies to */
|
|
13
|
+
tableName: string;
|
|
14
|
+
/** Selected field names */
|
|
15
|
+
columns: string[];
|
|
16
|
+
/** The filter conditions */
|
|
17
|
+
filters: SearchFilters;
|
|
18
|
+
/** Sort order configuration */
|
|
19
|
+
sortOrder: SortOrder[];
|
|
20
|
+
/** Selected relation field names */
|
|
21
|
+
selectedRelations: string[];
|
|
22
|
+
/** Expanded relation fields (manyToOne fields shown as inline columns) */
|
|
23
|
+
expandedRelationFields: ExpandedRelationFields;
|
|
24
|
+
/** When this view was created */
|
|
25
|
+
createdAt: Date;
|
|
26
|
+
/** When this view was last updated */
|
|
27
|
+
updatedAt: Date;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Sort order configuration
|
|
32
|
+
*/
|
|
33
|
+
export interface SortOrder {
|
|
34
|
+
/** Field name to sort by */
|
|
35
|
+
field: string;
|
|
36
|
+
/** Sort direction */
|
|
37
|
+
direction: "asc" | "desc";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Input for creating or updating a saved view
|
|
42
|
+
*/
|
|
43
|
+
export interface SavedViewInput {
|
|
44
|
+
/** ID for updating an existing view (omit for create) */
|
|
45
|
+
id?: string;
|
|
46
|
+
/** User-defined name for the view */
|
|
47
|
+
name: string;
|
|
48
|
+
/** Table name this view applies to */
|
|
49
|
+
tableName: string;
|
|
50
|
+
/** Selected field names */
|
|
51
|
+
columns: string[];
|
|
52
|
+
/** The filter conditions */
|
|
53
|
+
filters: SearchFilters;
|
|
54
|
+
/** Sort order configuration */
|
|
55
|
+
sortOrder: SortOrder[];
|
|
56
|
+
/** Selected relation field names */
|
|
57
|
+
selectedRelations: string[];
|
|
58
|
+
/** Expanded relation fields */
|
|
59
|
+
expandedRelationFields: ExpandedRelationFields;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Store interface for saving/loading views
|
|
64
|
+
*/
|
|
65
|
+
export interface SavedViewStore {
|
|
66
|
+
/** Get all saved views */
|
|
67
|
+
listViews(): Promise<SavedView[]>;
|
|
68
|
+
|
|
69
|
+
/** Save a view (create new or update existing) */
|
|
70
|
+
saveView(view: SavedViewInput): Promise<SavedView>;
|
|
71
|
+
|
|
72
|
+
/** Delete a view by ID */
|
|
73
|
+
deleteView(viewId: string): Promise<void>;
|
|
74
|
+
|
|
75
|
+
/** Get a specific view by ID */
|
|
76
|
+
getView(viewId: string): Promise<SavedView | null>;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Factory function for creating a store
|
|
81
|
+
* Used for stores that need config from createDataViewModule (e.g., appUri)
|
|
82
|
+
*/
|
|
83
|
+
export type SavedViewStoreFactory = (config: {
|
|
84
|
+
appUri: string;
|
|
85
|
+
}) => SavedViewStore;
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Field type mapping for Data View
|
|
3
|
-
*/
|
|
4
|
-
export type FieldType =
|
|
5
|
-
| "string"
|
|
6
|
-
| "number"
|
|
7
|
-
| "boolean"
|
|
8
|
-
| "uuid"
|
|
9
|
-
| "datetime"
|
|
10
|
-
| "date"
|
|
11
|
-
| "time"
|
|
12
|
-
| "enum"
|
|
13
|
-
| "array"
|
|
14
|
-
| "nested";
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Metadata for a single field
|
|
18
|
-
*/
|
|
19
|
-
export interface FieldMetadata {
|
|
20
|
-
name: string;
|
|
21
|
-
type: FieldType;
|
|
22
|
-
required: boolean;
|
|
23
|
-
enumValues?: readonly string[];
|
|
24
|
-
arrayItemType?: FieldType;
|
|
25
|
-
description?: string;
|
|
26
|
-
/** manyToOne relation info (if this field is a foreign key) */
|
|
27
|
-
relation?: {
|
|
28
|
-
/** GraphQL field name for the related object (e.g., "task") */
|
|
29
|
-
fieldName: string;
|
|
30
|
-
/** Target table name in camelCase (e.g., "task") */
|
|
31
|
-
targetTable: string;
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Metadata for a relation
|
|
37
|
-
*/
|
|
38
|
-
export interface RelationMetadata {
|
|
39
|
-
/** GraphQL field name (e.g., "task" for manyToOne, "taskAssignments" for oneToMany) */
|
|
40
|
-
fieldName: string;
|
|
41
|
-
/** Target table name in camelCase (e.g., "task", "taskAssignment") */
|
|
42
|
-
targetTable: string;
|
|
43
|
-
/** Relation type */
|
|
44
|
-
relationType: "manyToOne" | "oneToMany";
|
|
45
|
-
/** For manyToOne: the FK field name (e.g., "taskId"). For oneToMany: the FK field on the child table */
|
|
46
|
-
foreignKeyField: string;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Metadata for a single table
|
|
51
|
-
*/
|
|
52
|
-
export interface TableMetadata {
|
|
53
|
-
name: string;
|
|
54
|
-
pluralForm: string;
|
|
55
|
-
description?: string;
|
|
56
|
-
readAllowedRoles: string[];
|
|
57
|
-
fields: FieldMetadata[];
|
|
58
|
-
/** Relations (manyToOne and oneToMany) */
|
|
59
|
-
relations?: RelationMetadata[];
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Map of all tables
|
|
64
|
-
*/
|
|
65
|
-
export type TableMetadataMap = Record<string, TableMetadata>;
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Expanded relation fields configuration
|
|
69
|
-
* Key: relation field name (e.g., "task")
|
|
70
|
-
* Value: array of selected field names from the related table (e.g., ["name", "status"])
|
|
71
|
-
*/
|
|
72
|
-
export type ExpandedRelationFields = Record<string, string[]>;
|
/package/{API.md → docs/API.md}
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|