Skip to content

Commit 63619c7

Browse files
committed
Enhance typegen configuration and UI components
- Updated typegen configuration to support field and table-level overrides, allowing for more granular control over generated types. - Introduced a new metadata.xml file to manage schema metadata, improving validation and user experience. - Enhanced the MetadataFieldsDialog and MetadataTablesEditor components with new features for field exclusion and type overrides. - Improved state management and user interactions in the UI, including the ability to include/exclude fields and tables easily. - Refactored data grid components for better performance and usability, ensuring a smoother user experience.
1 parent 482a868 commit 63619c7

File tree

10 files changed

+1151
-108
lines changed

10 files changed

+1151
-108
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,4 @@ server/dist
7272
public/dist
7373
.turbo
7474
packages/fmdapi/test/typegen/*
75+
packages/typegen/schema/metadata.xml

packages/typegen/proofkit-typegen.config.jsonc

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,31 @@
1515
"path": "schema",
1616
"metadataPath": "schema/metadata.xml",
1717
"downloadMetadata": true,
18-
"clearOldFiles": false
18+
"clearOldFiles": false,
19+
"tables": [
20+
{
21+
"tableName": "customer",
22+
"fields": [
23+
{
24+
"fieldName": "ModificationTimestamp",
25+
"exclude": true
26+
},
27+
{
28+
"fieldName": "ModifiedBy",
29+
"exclude": true
30+
}
31+
]
32+
},
33+
{
34+
"tableName": "contacts",
35+
"fields": [
36+
{
37+
"fieldName": "hobby",
38+
"exclude": true
39+
}
40+
]
41+
}
42+
]
1943
}
2044
]
2145
}

packages/typegen/schema/metadata.xml

Lines changed: 0 additions & 1 deletion
This file was deleted.

packages/typegen/src/fmodata/generateODataTypes.ts

Lines changed: 124 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,55 @@ interface GeneratedTO {
1212
needsZod: boolean;
1313
}
1414

15+
/**
16+
* Maps type override enum values to field builder functions from @proofkit/fmodata
17+
*/
18+
function mapTypeOverrideToFieldBuilder(
19+
typeOverride:
20+
| "text"
21+
| "number"
22+
| "boolean"
23+
| "fmBooleanNumber"
24+
| "date"
25+
| "timestamp"
26+
| "container",
27+
): string {
28+
switch (typeOverride) {
29+
case "text":
30+
return "textField()";
31+
case "number":
32+
return "numberField()";
33+
case "boolean":
34+
case "fmBooleanNumber":
35+
return "numberField().outputValidator(z.coerce.boolean())";
36+
case "date":
37+
return "dateField()";
38+
case "timestamp":
39+
return "timestampField()";
40+
case "container":
41+
return "containerField()";
42+
}
43+
}
44+
1545
/**
1646
* Maps OData types to field builder functions from @proofkit/fmodata
1747
*/
18-
function mapODataTypeToFieldBuilder(edmType: string): string {
48+
function mapODataTypeToFieldBuilder(
49+
edmType: string,
50+
typeOverride?:
51+
| "text"
52+
| "number"
53+
| "boolean"
54+
| "fmBooleanNumber"
55+
| "date"
56+
| "timestamp"
57+
| "container",
58+
): string {
59+
// If typeOverride is provided, use it instead of the inferred type
60+
if (typeOverride) {
61+
return mapTypeOverrideToFieldBuilder(typeOverride);
62+
}
63+
1964
switch (edmType) {
2065
case "Edm.String":
2166
return "textField()";
@@ -62,6 +107,7 @@ function generateTableOccurrence(
62107
entitySetName: string,
63108
entityType: EntityType,
64109
entityTypeToSetMap: Map<string, string>,
110+
tableOverride?: NonNullable<FmodataConfig["tables"]>[number],
65111
): GeneratedTO {
66112
const fmtId = entityType["@TableID"];
67113
const keyFields = entityType.$Key || [];
@@ -121,14 +167,60 @@ function generateTableOccurrence(
121167
idField = autoGenField ?? idFieldName ?? firstFieldName;
122168
}
123169

170+
// Build a field overrides map from the array for easier lookup
171+
type FieldOverrideType = Exclude<
172+
NonNullable<NonNullable<FmodataConfig["tables"]>[number]>["fields"],
173+
undefined
174+
>[number];
175+
const fieldOverridesMap = new Map<string, FieldOverrideType>();
176+
if (tableOverride?.fields) {
177+
for (const fieldOverride of tableOverride.fields) {
178+
if (fieldOverride?.fieldName) {
179+
fieldOverridesMap.set(fieldOverride.fieldName, fieldOverride);
180+
}
181+
}
182+
}
183+
124184
// Generate field builder definitions
125185
const fieldLines: string[] = [];
126186
const fieldEntries = Array.from(fields.entries());
127-
for (let i = 0; i < fieldEntries.length; i++) {
128-
const entry = fieldEntries[i];
187+
188+
// Filter out excluded fields and collect valid entries
189+
const validFieldEntries: Array<
190+
[string, typeof fields extends Map<infer K, infer V> ? V : never]
191+
> = [];
192+
for (const entry of fieldEntries) {
193+
if (!entry) continue;
194+
const [fieldName] = entry;
195+
const fieldOverride = fieldOverridesMap.get(fieldName);
196+
197+
// Skip excluded fields
198+
if (fieldOverride?.exclude === true) {
199+
continue;
200+
}
201+
202+
validFieldEntries.push(entry);
203+
}
204+
205+
for (let i = 0; i < validFieldEntries.length; i++) {
206+
const entry = validFieldEntries[i];
129207
if (!entry) continue;
130208
const [fieldName, metadata] = entry;
131-
const fieldBuilder = mapODataTypeToFieldBuilder(metadata.$Type);
209+
const fieldOverride = fieldOverridesMap.get(fieldName);
210+
211+
// Apply typeOverride if provided, otherwise use inferred type
212+
const fieldBuilder = mapODataTypeToFieldBuilder(
213+
metadata.$Type,
214+
fieldOverride?.typeOverride as
215+
| "text"
216+
| "number"
217+
| "boolean"
218+
| "fmBooleanNumber"
219+
| "date"
220+
| "timestamp"
221+
| "container"
222+
| undefined,
223+
);
132224

133225
// Track which field builders are used
134226
if (fieldBuilder.includes("textField()")) {
@@ -153,7 +245,7 @@ function generateTableOccurrence(
153245
// metadata.$Nullable is false only if Nullable="false" was in XML, otherwise it's true (nullable by default)
154246
const isExplicitlyNotNullable = metadata.$Nullable === false;
155247
const isReadOnly = readOnlyFields.includes(fieldName);
156-
const isLastField = i === fieldEntries.length - 1;
248+
const isLastField = i === validFieldEntries.length - 1;
157249

158250
let line = ` ${JSON.stringify(fieldName)}: ${fieldBuilder}`;
159251

@@ -181,7 +273,10 @@ function generateTableOccurrence(
181273
fieldLines.push(line);
182274
}
183275

184-
const varName = entitySetName.replace(/[^a-zA-Z0-9_]/g, "_");
276+
// Apply variableName override if provided, otherwise generate from entitySetName
277+
const varName = tableOverride?.variableName
278+
? tableOverride.variableName.replace(/[^a-zA-Z0-9_]/g, "_")
279+
: entitySetName.replace(/[^a-zA-Z0-9_]/g, "_");
185280

186281
// Build options object
187282
const optionsParts: string[] = [];
@@ -269,7 +364,7 @@ export async function generateODataTypes(
269364
config: FmodataConfig,
270365
): Promise<void> {
271366
const { entityTypes, entitySets } = metadata;
272-
const { path, clearOldFiles = true } = config;
367+
const { path, clearOldFiles = true, tables } = config;
273368
const outputPath = path ?? "schema";
274369

275370
// Build a map from entity type name to entity set name
@@ -278,16 +373,38 @@ export async function generateODataTypes(
278373
entityTypeToSetMap.set(entitySet.EntityType, entitySetName);
279374
}
280375

376+
// Build a table overrides map from the array for easier lookup
377+
const tableOverridesMap = new Map<
378+
string,
379+
NonNullable<FmodataConfig["tables"]>[number]
380+
>();
381+
if (tables) {
382+
for (const tableOverride of tables) {
383+
if (tableOverride?.tableName) {
384+
tableOverridesMap.set(tableOverride.tableName, tableOverride);
385+
}
386+
}
387+
}
388+
281389
// Generate table occurrences for entity sets
282390
const generatedTOs: GeneratedTO[] = [];
283391

284392
for (const [entitySetName, entitySet] of entitySets.entries()) {
393+
// Get table override config if it exists
394+
const tableOverride = tableOverridesMap.get(entitySetName);
395+
396+
// Skip excluded tables
397+
if (tableOverride?.exclude === true) {
398+
continue;
399+
}
400+
285401
const entityType = entityTypes.get(entitySet.EntityType);
286402
if (entityType) {
287403
const generated = generateTableOccurrence(
288404
entitySetName,
289405
entityType,
290406
entityTypeToSetMap,
407+
tableOverride,
291408
);
292409
generatedTOs.push(generated);
293410
}

packages/typegen/src/server/app.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { Hono } from "hono";
2-
import { logger } from "hono/logger";
32
import { zValidator } from "@hono/zod-validator";
43
import fs from "fs-extra";
54
import path from "path";
@@ -22,12 +21,6 @@ export interface ApiContext {
2221
configPath: string;
2322
}
2423

25-
export const devOnlyLogger = (message: string, ...rest: string[]) => {
26-
if (process.env.NODE_ENV === "development") {
27-
console.log(message, ...rest);
28-
}
29-
};
30-
3124
/**
3225
* Flattens a nested layout/folder structure into a flat list with full paths
3326
*/
@@ -61,7 +54,6 @@ function flattenLayouts(
6154
export function createApiApp(context: ApiContext) {
6255
// Define all routes with proper chaining for type inference
6356
const app = new Hono()
64-
.use(logger(devOnlyLogger))
6557
.basePath("/api")
6658

6759
// GET /api/config

packages/typegen/src/server/createDataApiClient.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -173,10 +173,6 @@ export function createOdataClientFromConfig(
173173
}
174174
const { server, db: dbName, authType, auth } = result;
175175

176-
console.log("server", server);
177-
console.log("dbName", dbName);
178-
console.log("authType", authType);
179-
console.log("auth", auth);
180176
const connection = new FMServerConnection({
181177
serverUrl: server,
182178
auth,

packages/typegen/src/types.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,62 @@ const path = z
8787
.optional()
8888
.meta({ description: "The folder path to output the generated files" });
8989

90+
// Field-level override configuration
91+
const fieldOverride = z
92+
.object({
93+
// Field name to apply override to
94+
fieldName: z.string().meta({
95+
description: "The field name this override applies to",
96+
}),
97+
// Exclude this field from generation
98+
exclude: z.boolean().optional().meta({
99+
description: "If true, this field will be excluded from generation",
100+
}),
101+
// Override the inferred type from metadata
102+
typeOverride: z
103+
.enum([
104+
"text", // textField()
105+
"number", // numberField()
106+
"boolean", // numberField().outputValidator(z.coerce.boolean())
107+
"fmBooleanNumber", // Same as boolean, explicit FileMaker 0/1 pattern
108+
"date", // dateField()
109+
"timestamp", // timestampField()
110+
"container", // containerField()
111+
])
112+
.optional()
113+
.meta({
114+
description:
115+
"Override the inferred field type from metadata. Options: text, number, boolean, fmBooleanNumber, date, timestamp, container",
116+
}),
117+
})
118+
.optional();
119+
120+
// Table-level override configuration
121+
const tableOverride = z
122+
.object({
123+
// Table name to apply override to
124+
tableName: z.string().meta({
125+
description:
126+
"The entity set name (table occurrence name) this override applies to",
127+
}),
128+
// Exclude entire table from generation
129+
exclude: z.boolean().optional().meta({
130+
description:
131+
"If true, this entire table will be excluded from generation",
132+
}),
133+
// Override the generated TypeScript variable name
134+
// (original entity set name is still used for the path)
135+
variableName: z.string().optional().meta({
136+
description:
137+
"Override the generated TypeScript variable name. The original entity set name is still used for the OData path.",
138+
}),
139+
// Field-specific overrides as an array
140+
fields: z.array(fieldOverride).optional().meta({
141+
description: "Field-specific overrides as an array",
142+
}),
143+
})
144+
.optional();
145+
90146
const typegenConfigSingleBase = z.discriminatedUnion("type", [
91147
z.object({
92148
type: z.literal("fmdapi"),
@@ -137,6 +193,10 @@ const typegenConfigSingleBase = z.discriminatedUnion("type", [
137193
description:
138194
"If false, the path will not be cleared before the new files are written. Only the `client` and `generated` directories are cleared to allow for potential overrides to be kept.",
139195
}),
196+
tables: z.array(tableOverride).optional().meta({
197+
description:
198+
"Table-specific overrides as an array. Allows excluding tables/fields, renaming variables, and overriding field types.",
199+
}),
140200
}),
141201
]);
142202

0 commit comments

Comments
 (0)