From 51287c9cb5ac7b286645745e5f6e2b947b36b08c Mon Sep 17 00:00:00 2001 From: Eric Luce <37158449+eluce2@users.noreply.github.com> Date: Fri, 19 Dec 2025 15:04:27 -0600 Subject: [PATCH 1/3] fix username/password auth --- .../typegen/proofkit-typegen.config.jsonc | 37 ++++++------ packages/typegen/src/buildLayoutClient.ts | 10 +--- .../typegen/src/server/createDataApiClient.ts | 38 +++++++------ packages/typegen/src/typegen.ts | 32 +++++------ packages/typegen/src/types.ts | 56 ++++++++----------- 5 files changed, 82 insertions(+), 91 deletions(-) diff --git a/packages/typegen/proofkit-typegen.config.jsonc b/packages/typegen/proofkit-typegen.config.jsonc index 2e5db011..f87f76b9 100644 --- a/packages/typegen/proofkit-typegen.config.jsonc +++ b/packages/typegen/proofkit-typegen.config.jsonc @@ -2,56 +2,61 @@ "config": [ { "type": "fmodata", + "envNames": { + "auth": { + "apiKey": "unused" + } + }, "path": "schema", "clearOldFiles": false, "alwaysOverrideFieldNames": true, "tables": [ { "tableName": "isolated_contacts", - "reduceMetadata": false, + "reduceMetadata": false }, { "tableName": "fmdapi_test", "fields": [ { "fieldName": "CreationTimestamp", - "exclude": true, + "exclude": true }, { "fieldName": "CreatedBy", - "exclude": true, + "exclude": true }, { "fieldName": "ModificationTimestamp", - "exclude": true, + "exclude": true }, { "fieldName": "ModifiedBy", - "exclude": true, + "exclude": true }, { "fieldName": "anything", - "exclude": true, + "exclude": true }, { "fieldName": "fieldWithValues", - "exclude": true, + "exclude": true }, { "fieldName": "myContainer", - "exclude": true, + "exclude": true }, { "fieldName": "repeatingContainer", - "exclude": true, + "exclude": true }, { "fieldName": "booleanField", - "exclude": true, - }, - ], - }, - ], - }, - ], + "exclude": true + } + ] + } + ] + } + ] } diff --git a/packages/typegen/src/buildLayoutClient.ts b/packages/typegen/src/buildLayoutClient.ts index 649032ca..efd9dc9e 100644 --- a/packages/typegen/src/buildLayoutClient.ts +++ b/packages/typegen/src/buildLayoutClient.ts @@ -64,19 +64,15 @@ export function buildLayoutClient( addTypeGuardStatements(sourceFile, { envVarName: envNames.server ?? defaultEnvNames.server, }); - if (typeof envNames.auth === "object" && "apiKey" in envNames.auth) { + if (typeof envNames.auth === "object") { addTypeGuardStatements(sourceFile, { envVarName: envNames.auth.apiKey ?? defaultEnvNames.apiKey, }); - } else if ( - typeof envNames.auth === "object" && - "username" in envNames.auth - ) { addTypeGuardStatements(sourceFile, { - envVarName: envNames.auth?.username ?? defaultEnvNames.username, + envVarName: envNames.auth.username ?? defaultEnvNames.username, }); addTypeGuardStatements(sourceFile, { - envVarName: envNames.auth?.password ?? defaultEnvNames.password, + envVarName: envNames.auth.password ?? defaultEnvNames.password, }); } } diff --git a/packages/typegen/src/server/createDataApiClient.ts b/packages/typegen/src/server/createDataApiClient.ts index 80141c7c..8cbcdd39 100644 --- a/packages/typegen/src/server/createDataApiClient.ts +++ b/packages/typegen/src/server/createDataApiClient.ts @@ -51,26 +51,31 @@ function getEnvVarsFromConfig( const getEnvName = (customName: string | undefined, defaultName: string) => customName && customName.trim() !== "" ? customName : defaultName; + console.log("env names", envNames); + // Resolve environment variables const server = process.env[getEnvName(envNames?.server, defaultEnvNames.server)]; const db = process.env[getEnvName(envNames?.db, defaultEnvNames.db)]; - const apiKey = - (envNames?.auth && "apiKey" in envNames.auth - ? process.env[getEnvName(envNames.auth.apiKey, defaultEnvNames.apiKey)] - : undefined) ?? process.env[defaultEnvNames.apiKey]; - const username = - (envNames?.auth && "username" in envNames.auth - ? process.env[ - getEnvName(envNames.auth.username, defaultEnvNames.username) - ] - : undefined) ?? process.env[defaultEnvNames.username]; - const password = - (envNames?.auth && "password" in envNames.auth - ? process.env[ - getEnvName(envNames.auth.password, defaultEnvNames.password) - ] - : undefined) ?? process.env[defaultEnvNames.password]; + + // Always attempt to read all auth methods from environment variables, + // regardless of which type is specified in envNames.auth + const apiKeyEnvName = + envNames?.auth && "apiKey" in envNames.auth + ? getEnvName(envNames.auth.apiKey, defaultEnvNames.apiKey) + : defaultEnvNames.apiKey; + const usernameEnvName = + envNames?.auth && "username" in envNames.auth + ? getEnvName(envNames.auth.username, defaultEnvNames.username) + : defaultEnvNames.username; + const passwordEnvName = + envNames?.auth && "password" in envNames.auth + ? getEnvName(envNames.auth.password, defaultEnvNames.password) + : defaultEnvNames.password; + + const apiKey = process.env[apiKeyEnvName]; + const username = process.env[usernameEnvName]; + const password = process.env[passwordEnvName]; // Validate required env vars if (!server || !db || (!apiKey && !username)) { @@ -168,6 +173,7 @@ export function createOdataClientFromConfig( config: FmodataConfig, ): OdataClientResult | OdataClientError { const result = getEnvVarsFromConfig(config.envNames); + console.log("env vars result", result); if ("error" in result) { return result; } diff --git a/packages/typegen/src/typegen.ts b/packages/typegen/src/typegen.ts index 69122863..ff63b191 100644 --- a/packages/typegen/src/typegen.ts +++ b/packages/typegen/src/typegen.ts @@ -166,24 +166,20 @@ const generateTypedClientsSingle = async ( webviewerScriptName: config?.type === "fmdapi" ? config.webviewerScriptName : undefined, envNames: { - auth: - "apiKey" in auth - ? { - apiKey: - envNames?.auth && "apiKey" in envNames.auth - ? (envNames.auth.apiKey ?? defaultEnvNames.apiKey) - : defaultEnvNames.apiKey, - } - : { - username: - envNames?.auth && "username" in envNames.auth - ? (envNames.auth.username ?? defaultEnvNames.username) - : defaultEnvNames.username, - password: - envNames?.auth && "password" in envNames.auth - ? (envNames.auth.password ?? defaultEnvNames.password) - : defaultEnvNames.password, - }, + auth: { + apiKey: + envNames?.auth && "apiKey" in envNames.auth + ? (envNames.auth.apiKey ?? defaultEnvNames.apiKey) + : defaultEnvNames.apiKey, + username: + envNames?.auth && "username" in envNames.auth + ? (envNames.auth.username ?? defaultEnvNames.username) + : defaultEnvNames.username, + password: + envNames?.auth && "password" in envNames.auth + ? (envNames.auth.password ?? defaultEnvNames.password) + : defaultEnvNames.password, + }, db: envNames?.db ?? defaultEnvNames.db, server: envNames?.server ?? defaultEnvNames.server, }, diff --git a/packages/typegen/src/types.ts b/packages/typegen/src/types.ts index ee2fe244..81ab2558 100644 --- a/packages/typegen/src/types.ts +++ b/packages/typegen/src/types.ts @@ -34,40 +34,28 @@ const envNames = z .string() .optional() .transform((val) => (val === "" ? undefined : val)), - auth: z.union([ - z - .object({ - apiKey: z - .string() - .optional() - .transform((val) => (val === "" ? undefined : val)), - }) - .optional() - .transform((val) => { - if (val && Object.values(val).every((v) => v === undefined)) { - return undefined; - } - return val ?? undefined; - }), - z - .object({ - username: z - .string() - .optional() - .transform((val) => (val === "" ? undefined : val)), - password: z - .string() - .optional() - .transform((val) => (val === "" ? undefined : val)), - }) - .optional() - .transform((val) => { - if (val && Object.values(val).every((v) => v === undefined)) { - return undefined; - } - return val ?? undefined; - }), - ]), + auth: z + .object({ + apiKey: z + .string() + .optional() + .transform((val) => (val === "" ? undefined : val)), + username: z + .string() + .optional() + .transform((val) => (val === "" ? undefined : val)), + password: z + .string() + .optional() + .transform((val) => (val === "" ? undefined : val)), + }) + .optional() + .transform((val) => { + if (!val || Object.values(val).every((v) => v === undefined)) { + return undefined; + } + return val; + }), }) .optional() .transform((val) => { From 7d472d49ea751748d30a0b26a4e607574a11772b Mon Sep 17 00:00:00 2001 From: Eric Luce <37158449+eluce2@users.noreply.github.com> Date: Fri, 19 Dec 2025 15:19:51 -0600 Subject: [PATCH 2/3] fix tests --- .../typegen/src/fmodata/generateODataTypes.ts | 84 ++++- packages/typegen/src/getEnvValues.ts | 120 +++++-- .../typegen/src/server/createDataApiClient.ts | 3 - .../__snapshots__/zod-layout-client.snap.ts | 2 +- .../web/src/components/ConnectionWarning.tsx | 2 +- .../src/components/MetadataFieldsDialog.tsx | 20 +- .../src/components/MetadataTablesEditor.tsx | 117 ++++-- .../components/data-grid/sticky-header.tsx | 338 ++++++++++++++++++ pnpm-lock.yaml | 102 ++++-- 9 files changed, 673 insertions(+), 115 deletions(-) create mode 100644 packages/typegen/web/src/components/data-grid/sticky-header.tsx diff --git a/packages/typegen/src/fmodata/generateODataTypes.ts b/packages/typegen/src/fmodata/generateODataTypes.ts index 7b86534b..ad7dc9db 100644 --- a/packages/typegen/src/fmodata/generateODataTypes.ts +++ b/packages/typegen/src/fmodata/generateODataTypes.ts @@ -53,6 +53,43 @@ function mapTypeOverrideToFieldBuilder( } } +/** + * Applies import aliases to a field builder expression + * e.g., "textField()" with alias "textField" -> "tf" becomes "tf()" + */ +function applyAliasToFieldBuilder( + fieldBuilder: string, + importAliases: Map | undefined, +): string { + if (!importAliases || importAliases.size === 0) { + return fieldBuilder; + } + + // Map of field builder function names to their import names + const fieldBuilderMap = new Map([ + ["textField", "textField"], + ["numberField", "numberField"], + ["dateField", "dateField"], + ["timestampField", "timestampField"], + ["containerField", "containerField"], + ]); + + // Try to find and replace each field builder with its alias + let result = fieldBuilder; + for (const [baseName, importName] of fieldBuilderMap) { + const alias = importAliases.get(baseName); + if (alias) { + // Replace "baseName(" with "alias(" + result = result.replace( + new RegExp(`\\b${baseName}\\(`, "g"), + `${alias}(`, + ); + } + } + + return result; +} + /** * Maps OData types to field builder functions from @proofkit/fmodata */ @@ -121,6 +158,7 @@ function generateTableOccurrence( tableOverride?: NonNullable[number], existingFields?: ParsedTableOccurrence, alwaysOverrideFieldNames?: boolean, + importAliases?: Map, // Map base name -> alias (e.g., "textField" -> "tf") ): GeneratedTO { const fmtId = entityType["@TableID"]; const keyFields = entityType.$Key || []; @@ -250,7 +288,7 @@ function generateTableOccurrence( } // Apply typeOverride if provided, otherwise use inferred type - const fieldBuilder = mapODataTypeToFieldBuilder( + let fieldBuilder = mapODataTypeToFieldBuilder( metadata.$Type, fieldOverride?.typeOverride as | "text" @@ -263,6 +301,9 @@ function generateTableOccurrence( | undefined, ); + // Apply import aliases if present + fieldBuilder = applyAliasToFieldBuilder(fieldBuilder, importAliases); + // Track which field builders are used if (fieldBuilder.includes("textField()")) { usedFieldBuilders.add("textField"); @@ -433,6 +474,7 @@ interface ParsedTableOccurrence { fields: Map; // keyed by field name fieldsByEntityId: Map; // keyed by entity ID existingImports: string[]; // All existing import statements as strings + importAliases: Map; // Map base name -> alias (e.g., "textField" -> "tf") } /** @@ -725,14 +767,26 @@ function parseExistingTableFile( } } - // Extract existing imports + // Extract existing imports and build alias map const existingImports: string[] = []; + const importAliases = new Map(); // base name -> alias const importDeclarations = sourceFile.getImportDeclarations(); for (const importDecl of importDeclarations) { const importText = importDecl.getFullText(); if (importText.trim()) { existingImports.push(importText.trim()); } + + // Extract aliases from named imports + const namedImports = importDecl.getNamedImports(); + for (const namedImport of namedImports) { + const name = namedImport.getName(); // The original name + const aliasNode = namedImport.getAliasNode(); + if (aliasNode) { + const alias = aliasNode.getText(); // The alias + importAliases.set(name, alias); + } + } } // Parse each field @@ -785,6 +839,7 @@ function parseExistingTableFile( fields, fieldsByEntityId, existingImports, + importAliases, }; } @@ -937,6 +992,7 @@ export async function generateODataTypes( tableOverride, undefined, tableAlwaysOverrideFieldNames, + undefined, ); generatedTOs.push({ ...generated, @@ -995,6 +1051,7 @@ export async function generateODataTypes( generated.tableOverride, existingFields, tableAlwaysOverrideFieldNames, + existingFields.importAliases, ) : generated; @@ -1194,8 +1251,15 @@ export async function generateODataTypes( }); } - // Add missing required imports (without aliases) - importSpecs.push(...missingImports); + // Add missing required imports (apply aliases if they exist) + for (const missingImport of missingImports) { + const alias = existingFields.importAliases.get(missingImport); + if (alias) { + importSpecs.push(`${missingImport} as ${alias}`); + } else { + importSpecs.push(missingImport); + } + } // Sort imports (but preserve aliases) importSpecs.sort(); @@ -1259,7 +1323,17 @@ export async function generateODataTypes( // Add any required imports that don't exist yet for (const [module, namedImports] of requiredImportsByModule.entries()) { if (module && !handledModules.has(module)) { - const importsList = Array.from(namedImports).sort().join(", "); + // Apply aliases to new imports if they exist + const importSpecs: string[] = []; + for (const importName of Array.from(namedImports).sort()) { + const alias = existingFields.importAliases.get(importName); + if (alias) { + importSpecs.push(`${importName} as ${alias}`); + } else { + importSpecs.push(importName); + } + } + const importsList = importSpecs.join(", "); if (importsList) { finalImportLines.push( `import { ${importsList} } from "${module}";`, diff --git a/packages/typegen/src/getEnvValues.ts b/packages/typegen/src/getEnvValues.ts index fe155da9..e093d72f 100644 --- a/packages/typegen/src/getEnvValues.ts +++ b/packages/typegen/src/getEnvValues.ts @@ -33,25 +33,34 @@ export type EnvValidationResult = * @returns Object containing all environment variable values */ export function getEnvValues(envNames?: EnvNames): EnvValues { - const server = process.env[envNames?.server ?? defaultEnvNames.server]; - const db = process.env[envNames?.db ?? defaultEnvNames.db]; - - // For apiKey, check custom env name first, then fall back to default - // This matches the pattern in typegen.ts - const apiKey = - (envNames?.auth && "apiKey" in envNames.auth - ? process.env[envNames.auth.apiKey ?? defaultEnvNames.apiKey] - : undefined) ?? process.env[defaultEnvNames.apiKey]; - - const username = - (envNames?.auth && "username" in envNames.auth - ? process.env[envNames.auth.username ?? defaultEnvNames.username] - : undefined) ?? process.env[defaultEnvNames.username]; - - const password = - (envNames?.auth && "password" in envNames.auth - ? process.env[envNames.auth.password ?? defaultEnvNames.password] - : undefined) ?? process.env[defaultEnvNames.password]; + // Helper to get env name, treating empty strings as undefined + const getEnvName = (customName: string | undefined, defaultName: string) => + customName && customName.trim() !== "" ? customName : defaultName; + + // Resolve environment variables + const server = + process.env[getEnvName(envNames?.server, defaultEnvNames.server)]; + const db = process.env[getEnvName(envNames?.db, defaultEnvNames.db)]; + + // Always attempt to read all auth methods from environment variables, + // regardless of which type is specified in envNames.auth + // This matches the pattern in getEnvVarsFromConfig + const apiKeyEnvName = + envNames?.auth && "apiKey" in envNames.auth + ? getEnvName(envNames.auth.apiKey, defaultEnvNames.apiKey) + : defaultEnvNames.apiKey; + const usernameEnvName = + envNames?.auth && "username" in envNames.auth + ? getEnvName(envNames.auth.username, defaultEnvNames.username) + : defaultEnvNames.username; + const passwordEnvName = + envNames?.auth && "password" in envNames.auth + ? getEnvName(envNames.auth.password, defaultEnvNames.password) + : defaultEnvNames.password; + + const apiKey = process.env[apiKeyEnvName]; + const username = process.env[usernameEnvName]; + const password = process.env[passwordEnvName]; return { server, @@ -64,7 +73,7 @@ export function getEnvValues(envNames?: EnvNames): EnvValues { /** * Validates environment values and returns a result with either success data or error message. - * Uses chalk for console output (for fmdapi compatibility). + * Follows the same validation pattern as getEnvVarsFromConfig for consistency. * * @param envValues - The environment values to validate * @param envNames - Optional custom environment variable names (for error messages) @@ -76,29 +85,61 @@ export function validateEnvValues( ): EnvValidationResult { const { server, db, apiKey, username, password } = envValues; + // Helper to get env name, treating empty strings as undefined + const getEnvName = (customName: string | undefined, defaultName: string) => + customName && customName.trim() !== "" ? customName : defaultName; + + // Validate required env vars (server, db, and at least one auth method) if (!server || !db || (!apiKey && !username)) { + // Build missing details + const missingDetails: { + server?: boolean; + db?: boolean; + auth?: boolean; + password?: boolean; + } = { + server: !server, + db: !db, + auth: !apiKey && !username, + }; + + // Only report password as missing if server and db are both present, + // and username is set but password is missing. This ensures we don't + // incorrectly report password as missing when the actual error is about + // missing server or database. + if (server && db && username && !password) { + missingDetails.password = true; + } + + // Build error message with env var names const missingVars: string[] = []; if (!server) { - missingVars.push(envNames?.server ?? defaultEnvNames.server); + missingVars.push(getEnvName(envNames?.server, defaultEnvNames.server)); } if (!db) { - missingVars.push(envNames?.db ?? defaultEnvNames.db); + missingVars.push(getEnvName(envNames?.db, defaultEnvNames.db)); } - if (!apiKey) { + if (!apiKey && !username) { // Determine the names to display in the error message - const apiKeyName = - envNames?.auth && "apiKey" in envNames.auth && envNames.auth.apiKey + const apiKeyName = getEnvName( + envNames?.auth && "apiKey" in envNames.auth ? envNames.auth.apiKey - : defaultEnvNames.apiKey; - const usernameName = - envNames?.auth && "username" in envNames.auth && envNames.auth.username + : undefined, + defaultEnvNames.apiKey, + ); + const usernameName = getEnvName( + envNames?.auth && "username" in envNames.auth ? envNames.auth.username - : defaultEnvNames.username; - const passwordName = - envNames?.auth && "password" in envNames.auth && envNames.auth.password + : undefined, + defaultEnvNames.username, + ); + const passwordName = getEnvName( + envNames?.auth && "password" in envNames.auth ? envNames.auth.password - : defaultEnvNames.password; + : undefined, + defaultEnvNames.password, + ); missingVars.push( `${apiKeyName} (or ${usernameName} and ${passwordName})`, @@ -111,6 +152,21 @@ export function validateEnvValues( }; } + // Validate password if username is provided + if (username && !password) { + const passwordName = getEnvName( + envNames?.auth && "password" in envNames.auth + ? envNames.auth.password + : undefined, + defaultEnvNames.password, + ); + + return { + success: false, + errorMessage: `Password is required when using username authentication. Missing: ${passwordName}`, + }; + } + const auth: { apiKey: string } | { username: string; password: string } = apiKey ? { apiKey } diff --git a/packages/typegen/src/server/createDataApiClient.ts b/packages/typegen/src/server/createDataApiClient.ts index 8cbcdd39..8585a8bd 100644 --- a/packages/typegen/src/server/createDataApiClient.ts +++ b/packages/typegen/src/server/createDataApiClient.ts @@ -51,8 +51,6 @@ function getEnvVarsFromConfig( const getEnvName = (customName: string | undefined, defaultName: string) => customName && customName.trim() !== "" ? customName : defaultName; - console.log("env names", envNames); - // Resolve environment variables const server = process.env[getEnvName(envNames?.server, defaultEnvNames.server)]; @@ -173,7 +171,6 @@ export function createOdataClientFromConfig( config: FmodataConfig, ): OdataClientResult | OdataClientError { const result = getEnvVarsFromConfig(config.envNames); - console.log("env vars result", result); if ("error" in result) { return result; } diff --git a/packages/typegen/tests/__snapshots__/zod-layout-client.snap.ts b/packages/typegen/tests/__snapshots__/zod-layout-client.snap.ts index 050d9f46..e2fef985 100644 --- a/packages/typegen/tests/__snapshots__/zod-layout-client.snap.ts +++ b/packages/typegen/tests/__snapshots__/zod-layout-client.snap.ts @@ -3,7 +3,7 @@ * https://proofkit.dev/docs/typegen * DO NOT EDIT THIS FILE DIRECTLY. Changes may be overritten */ -import { z } from "zod/v4"; +import { z } from "zod"; import type { InferZodPortals } from "@proofkit/fmdapi"; // @generated diff --git a/packages/typegen/web/src/components/ConnectionWarning.tsx b/packages/typegen/web/src/components/ConnectionWarning.tsx index cc188a9b..d89d7f38 100644 --- a/packages/typegen/web/src/components/ConnectionWarning.tsx +++ b/packages/typegen/web/src/components/ConnectionWarning.tsx @@ -24,7 +24,7 @@ export function ConnectionWarning({ onRefresh }: ConnectionWarningProps) { UI Server Unavailable

- Did you stop the @proofkit/ui command? + Did you stop the @proofkit/typegen ui command?

diff --git a/packages/typegen/web/src/components/MetadataFieldsDialog.tsx b/packages/typegen/web/src/components/MetadataFieldsDialog.tsx index 8803fc5c..cb2a69d4 100644 --- a/packages/typegen/web/src/components/MetadataFieldsDialog.tsx +++ b/packages/typegen/web/src/components/MetadataFieldsDialog.tsx @@ -6,13 +6,12 @@ import { getCoreRowModel, getSortedRowModel, getFilteredRowModel, - getPaginationRowModel, type ColumnDef, } from "@tanstack/react-table"; import { DataGrid, DataGridContainer } from "./ui/data-grid"; import { DataGridTable } from "./ui/data-grid-table"; import { DataGridColumnHeader } from "./ui/data-grid-column-header"; -import { DataGridPagination } from "./ui/data-grid-pagination"; +import { ScrollArea, ScrollBar } from "./ui/scroll-area"; import { Input, InputWrapper } from "./ui/input"; import { Switch } from "./ui/switch"; import { Skeleton } from "./ui/skeleton"; @@ -46,7 +45,6 @@ import { const coreRowModel = getCoreRowModel(); const sortedRowModel = getSortedRowModel(); const filteredRowModel = getFilteredRowModel(); -const paginationRowModel = getPaginationRowModel(); // Stable empty array to prevent infinite re-renders const EMPTY_FIELDS_CONFIG: any[] = []; @@ -869,17 +867,11 @@ export function MetadataFieldsDialog({ getCoreRowModel: coreRowModel, getSortedRowModel: sortedRowModel, getFilteredRowModel: filteredRowModel, - getPaginationRowModel: paginationRowModel, globalFilterFn: "includesString", state: { globalFilter, }, onGlobalFilterChange: setGlobalFilter, - initialState: { - pagination: { - pageSize: 10, - }, - }, }); // Calculate the number of included (non-excluded) fields @@ -930,13 +922,13 @@ export function MetadataFieldsDialog({ recordCount={fieldsTable.getFilteredRowModel().rows.length} isLoading={isLoading} emptyMessage="No fields found." - tableLayout={{ width: "auto" }} + tableLayout={{ width: "auto", headerSticky: true }} > - -

- -
+ + + + )} diff --git a/packages/typegen/web/src/components/MetadataTablesEditor.tsx b/packages/typegen/web/src/components/MetadataTablesEditor.tsx index 114b2259..4983145a 100644 --- a/packages/typegen/web/src/components/MetadataTablesEditor.tsx +++ b/packages/typegen/web/src/components/MetadataTablesEditor.tsx @@ -14,14 +14,15 @@ import { getCoreRowModel, getSortedRowModel, getFilteredRowModel, - getPaginationRowModel, type ColumnDef, } from "@tanstack/react-table"; import { DataGrid, DataGridContainer } from "./ui/data-grid"; import { DataGridTable } from "./ui/data-grid-table"; import { DataGridColumnHeader } from "./ui/data-grid-column-header"; -import { DataGridPagination } from "./ui/data-grid-pagination"; +import { ScrollArea, ScrollBar } from "./ui/scroll-area"; import { Skeleton } from "./ui/skeleton"; +import { Badge } from "./ui/badge"; +import { DropdownMenuItem } from "./ui/dropdown-menu"; interface MetadataTablesEditorProps { configIndex: number; @@ -38,7 +39,6 @@ interface TableRow { const coreRowModel = getCoreRowModel(); const sortedRowModel = getSortedRowModel(); const filteredRowModel = getFilteredRowModel(); -const paginationRowModel = getPaginationRowModel(); // Helper component to fetch and display field count for a table function FieldCountCell({ @@ -228,6 +228,57 @@ export function MetadataTablesEditor({ [configIndex, setValue], ); + // Helper to include all tables + const includeAllTables = useCallback(() => { + if (!tables || tables.length === 0) return; + const currentConfig = configRef.current; + if (currentConfig?.type !== "fmodata") return; + + const currentTables = currentConfig.tables ?? []; + const currentTableNames = new Set( + currentTables.map((t) => t?.tableName).filter(Boolean), + ); + + // Add all tables that aren't already included + const tablesToAdd = tables.filter( + (tableName) => !currentTableNames.has(tableName), + ); + if (tablesToAdd.length > 0) { + const newTables = [ + ...currentTables, + ...tablesToAdd.map((tableName) => ({ tableName })), + ]; + setValue(`config.${configIndex}.tables` as any, newTables, { + shouldDirty: true, + }); + } + }, [tables, configIndex, setValue]); + + // Helper to exclude all tables + const excludeAllTables = useCallback(() => { + const currentConfig = configRef.current; + if (currentConfig?.type !== "fmodata") return; + + setValue(`config.${configIndex}.tables` as any, undefined, { + shouldDirty: true, + }); + }, [configIndex, setValue]); + + // Calculate if all tables are included/excluded + const allIncluded = useMemo(() => { + if (!tables || tables.length === 0) return false; + return tables.every((tableName) => + tablesConfig.some((t) => t?.tableName === tableName), + ); + }, [tables, tablesConfig]); + + const allExcluded = useMemo(() => { + if (!tables || tables.length === 0) return true; + return tables.every( + (tableName) => !tablesConfig.some((t) => t?.tableName === tableName), + ); + }, [tables, tablesConfig]); + // Convert tables to table rows (filtering will be handled by DataGrid) const tableRows = useMemo(() => { if (!tables) return []; @@ -243,10 +294,31 @@ export function MetadataTablesEditor({ { accessorKey: "isIncluded", header: ({ column }) => ( - + + + Include All + + + Exclude All + + + } + /> ), enableSorting: true, - size: 100, + size: 45, + minSize: 45, + maxSize: 45, cell: (info) => { const row = info.row.original; return ( @@ -262,6 +334,7 @@ export function MetadataTablesEditor({ }, meta: { skeleton: , + cellClassName: "!w-[45px] !min-w-[45px] !max-w-[45px]", }, }, { @@ -340,7 +413,14 @@ export function MetadataTablesEditor({ }, }, ], - [toggleTableInclude], + [ + toggleTableInclude, + includeAllTables, + excludeAllTables, + allIncluded, + allExcluded, + isLoadingTables, + ], ); // Create tables table instance @@ -350,17 +430,11 @@ export function MetadataTablesEditor({ getCoreRowModel: coreRowModel, getSortedRowModel: sortedRowModel, getFilteredRowModel: filteredRowModel, - getPaginationRowModel: paginationRowModel, globalFilterFn: "includesString", state: { globalFilter: searchFilter, }, onGlobalFilterChange: setSearchFilter, - initialState: { - pagination: { - pageSize: 10, - }, - }, }); // Show loading state only when actively loading @@ -476,7 +550,12 @@ export function MetadataTablesEditor({ <>
-

OData Tables

+

+ OData Tables{" "} + + {tableRows.filter((t) => t.isIncluded).length} selected + +

diff --git a/packages/typegen/web/src/components/data-grid/sticky-header.tsx b/packages/typegen/web/src/components/data-grid/sticky-header.tsx new file mode 100644 index 00000000..dc17bb09 --- /dev/null +++ b/packages/typegen/web/src/components/data-grid/sticky-header.tsx @@ -0,0 +1,338 @@ +import { useMemo, useState } from 'react'; +import { + Avatar, + AvatarFallback, + AvatarImage, + AvatarIndicator, + AvatarStatus, + avatarStatusVariants, +} from '@/components/ui/avatar'; +import { Badge } from '@/components/ui/badge'; +import { DataGrid, DataGridContainer } from '@/components/ui/data-grid'; +import { DataGridPagination } from '@/components/ui/data-grid-pagination'; +import { DataGridTable } from '@/components/ui/data-grid-table'; +import { ScrollArea, ScrollBar } from '@/components/ui/scroll-area'; +import { + ColumnDef, + getCoreRowModel, + getFilteredRowModel, + getPaginationRowModel, + getSortedRowModel, + PaginationState, + SortingState, + useReactTable, +} from '@tanstack/react-table'; + +interface IData { + id: string; + name: string; + availability: 'online' | 'away' | 'busy' | 'offline'; + avatar: string; + status: 'active' | 'inactive'; + flag: string; // Emoji flags + email: string; + company: string; + role: string; + joined: string; + location: string; + balance: number; +} + +const demoData: IData[] = [ + { + id: '1', + name: 'Kathryn Campbell', + availability: 'online', + avatar: '1.png', + status: 'active', + flag: '🇺🇸', + email: 'kathryn@apple.com', + company: 'Apple', + role: 'CEO', + joined: '2021-04-15', + location: 'San Francisco, USA', + balance: 5143.03, + }, + { + id: '2', + name: 'Robert Smith', + availability: 'away', + avatar: '2.png', + status: 'inactive', + flag: '🇬🇧', + email: 'robert@openai.com', + company: 'OpenAI', + role: 'CTO', + joined: '2020-07-20', + location: 'London, UK', + balance: 4321.87, + }, + { + id: '3', + name: 'Sophia Johnson', + availability: 'busy', + avatar: '3.png', + status: 'active', + flag: '🇨🇦', + email: 'sophia@meta.com', + company: 'Meta', + role: 'Designer', + joined: '2019-03-12', + location: 'Toronto, Canada', + balance: 7654.98, + }, + { + id: '4', + name: 'Lucas Walker', + availability: 'offline', + avatar: '4.png', + status: 'inactive', + flag: '🇦🇺', + email: 'lucas@tesla.com', + company: 'Tesla', + role: 'Developer', + joined: '2022-01-18', + location: 'Sydney, Australia', + balance: 3456.45, + }, + { + id: '5', + name: 'Emily Davis', + availability: 'online', + avatar: '5.png', + status: 'active', + flag: '🇩🇪', + email: 'emily@sap.com', + company: 'SAP', + role: 'Lawyer', + joined: '2023-05-23', + location: 'Berlin, Germany', + balance: 9876.54, + }, + { + id: '6', + name: 'James Lee', + availability: 'away', + avatar: '6.png', + status: 'active', + flag: '🇲🇾', + email: 'james@keenthemes.com', + company: 'Keenthemes', + role: 'Director', + joined: '2018-11-30', + location: 'Kuala Lumpur, MY', + balance: 6214.22, + }, + { + id: '7', + name: 'Isabella Martinez', + availability: 'busy', + avatar: '7.png', + status: 'inactive', + flag: '🇪🇸', + email: 'isabella@bbva.es', + company: 'BBVA', + role: 'Product Manager', + joined: '2021-06-14', + location: 'Barcelona, Spain', + balance: 5321.77, + }, + { + id: '8', + name: 'Benjamin Harris', + availability: 'offline', + avatar: '8.png', + status: 'active', + flag: '🇯🇵', + email: 'benjamin@sony.jp', + company: 'Sony', + role: 'Marketing Lead', + joined: '2020-10-22', + location: 'Tokyo, Japan', + balance: 8452.39, + }, + { + id: '9', + name: 'Olivia Brown', + availability: 'online', + avatar: '9.png', + status: 'active', + flag: '🇫🇷', + email: 'olivia@lvmh.fr', + company: 'LVMH', + role: 'Data Scientist', + joined: '2019-09-17', + location: 'Paris, France', + balance: 7345.1, + }, + { + id: '10', + name: 'Michael Clark', + availability: 'away', + avatar: '10.png', + status: 'inactive', + flag: '🇮🇹', + email: 'michael@eni.it', + company: 'ENI', + role: 'Engineer', + joined: '2023-02-11', + location: 'Milan, Italy', + balance: 5214.88, + }, + { + id: '11', + name: 'Ava Wilson', + availability: 'busy', + avatar: '11.png', + status: 'active', + flag: '🇧🇷', + email: 'ava@vale.br', + company: 'Vale', + role: 'Software Engineer', + joined: '2022-12-01', + location: 'Rio de Janeiro, Brazil', + balance: 9421.5, + }, + { + id: '12', + name: 'David Young', + availability: 'offline', + avatar: '12.png', + status: 'active', + flag: '🇮🇳', + email: 'david@tata.in', + company: 'Tata', + role: 'Sales Manager', + joined: '2020-03-27', + location: 'Mumbai, India', + balance: 4521.67, + }, +]; + +export default function DataGridDemo() { + const [pagination, setPagination] = useState({ + pageIndex: 0, + pageSize: 10, + }); + const [sorting, setSorting] = useState([{ id: 'name', desc: true }]); + + const columns = useMemo[]>( + () => [ + { + accessorKey: 'name', + id: 'name', + header: 'Name', + cell: ({ row }) => { + const availability = row.original.availability as keyof typeof avatarStatusVariants; + + return ( +
+ + + N + + + + +
+
{row.original.name}
+
{row.original.email}
+
+
+ ); + }, + size: 225, + enableSorting: true, + enableHiding: false, + }, + { + accessorKey: 'company', + id: 'company', + header: 'Role', + cell: ({ row }) => { + return ( +
+
{row.original.role}
+
{row.original.company}
+
+ ); + }, + size: 150, + enableSorting: true, + enableHiding: false, + }, + { + accessorKey: 'location', + header: 'Location', + cell: ({ row }) => { + return ( +
+ {row.original.flag} +
{row.original.location}
+
+ ); + }, + size: 160, + meta: { + headerClassName: '', + cellClassName: 'text-start', + }, + }, + { + accessorKey: 'status', + id: 'status', + header: 'Status', + cell: ({ row }) => { + const status = row.original.status; + + if (status == 'active') { + return ( + + Approved + + ); + } else { + return ( + + Pending + + ); + } + }, + size: 100, + }, + ], + [], + ); + + const table = useReactTable({ + columns, + data: demoData, + pageCount: Math.ceil((demoData?.length || 0) / pagination.pageSize), + getRowId: (row: IData) => row.id, + state: { + pagination, + sorting, + }, + onPaginationChange: setPagination, + onSortingChange: setSorting, + getCoreRowModel: getCoreRowModel(), + getFilteredRowModel: getFilteredRowModel(), + getPaginationRowModel: getPaginationRowModel(), + getSortedRowModel: getSortedRowModel(), + }); + + return ( + +
+ + + + + + + +
+
+ ); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5dc7083d..c64fa2a2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -149,22 +149,22 @@ importers: version: 2.1.1 fumadocs-core: specifier: 15.7.13 - version: 15.7.13(@types/react@19.2.7)(next@16.1.0(@babel/core@7.27.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + version: 15.7.13(@types/react@19.2.7)(next@16.1.0(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) fumadocs-docgen: specifier: ^2.1.0 version: 2.1.0 fumadocs-mdx: specifier: 11.6.4 - version: 11.6.4(acorn@8.14.1)(fumadocs-core@15.7.13(@types/react@19.2.7)(next@16.1.0(@babel/core@7.27.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(next@16.1.0(@babel/core@7.27.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)) + version: 11.6.4(acorn@8.14.1)(fumadocs-core@15.7.13(@types/react@19.2.7)(next@16.1.0(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(next@16.1.0(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)) fumadocs-twoslash: specifier: ^3.1.7 - version: 3.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(fumadocs-ui@15.7.13(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(next@16.1.0(@babel/core@7.27.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tailwindcss@4.1.11))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) + version: 3.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(fumadocs-ui@15.7.13(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(next@16.1.0(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tailwindcss@4.1.11))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) fumadocs-typescript: specifier: ^4.0.8 - version: 4.0.8(@types/react@19.2.7)(fumadocs-core@15.7.13(@types/react@19.2.7)(next@16.1.0(@babel/core@7.27.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(fumadocs-ui@15.7.13(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(next@16.1.0(@babel/core@7.27.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tailwindcss@4.1.11))(typescript@5.9.3) + version: 4.0.8(@types/react@19.2.7)(fumadocs-core@15.7.13(@types/react@19.2.7)(next@16.1.0(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(fumadocs-ui@15.7.13(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(next@16.1.0(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tailwindcss@4.1.11))(typescript@5.9.3) fumadocs-ui: specifier: 15.7.13 - version: 15.7.13(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(next@16.1.0(@babel/core@7.27.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tailwindcss@4.1.11) + version: 15.7.13(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(next@16.1.0(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tailwindcss@4.1.11) hono: specifier: ^4.9.0 version: 4.9.0 @@ -176,7 +176,7 @@ importers: version: 0.511.0(react@19.2.3) next: specifier: 16.1.0 - version: 16.1.0(@babel/core@7.27.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + version: 16.1.0(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) next-themes: specifier: ^0.4.6 version: 0.4.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -443,7 +443,7 @@ importers: version: 11.0.0-rc.441(@trpc/server@11.0.0-rc.441) '@trpc/next': specifier: 11.0.0-rc.441 - version: 11.0.0-rc.441(@tanstack/react-query@5.76.1(react@19.1.1))(@trpc/client@11.0.0-rc.441(@trpc/server@11.0.0-rc.441))(@trpc/react-query@11.0.0-rc.441(@tanstack/react-query@5.76.1(react@19.1.1))(@trpc/client@11.0.0-rc.441(@trpc/server@11.0.0-rc.441))(@trpc/server@11.0.0-rc.441)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@trpc/server@11.0.0-rc.441)(next@15.5.8(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 11.0.0-rc.441(@tanstack/react-query@5.76.1(react@19.1.1))(@trpc/client@11.0.0-rc.441(@trpc/server@11.0.0-rc.441))(@trpc/react-query@11.0.0-rc.441(@tanstack/react-query@5.76.1(react@19.1.1))(@trpc/client@11.0.0-rc.441(@trpc/server@11.0.0-rc.441))(@trpc/server@11.0.0-rc.441)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@trpc/server@11.0.0-rc.441)(next@15.5.8(@babel/core@7.28.5)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@trpc/react-query': specifier: 11.0.0-rc.441 version: 11.0.0-rc.441(@tanstack/react-query@5.76.1(react@19.1.1))(@trpc/client@11.0.0-rc.441(@trpc/server@11.0.0-rc.441))(@trpc/server@11.0.0-rc.441)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) @@ -485,10 +485,10 @@ importers: version: 3.14.1 next: specifier: ^15.5.8 - version: 15.5.8(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 15.5.8(@babel/core@7.28.5)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) next-auth: specifier: ^4.24.7 - version: 4.24.11(next@15.5.8(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 4.24.11(next@15.5.8(@babel/core@7.28.5)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1) postgres: specifier: ^3.4.4 version: 3.4.5 @@ -13558,11 +13558,11 @@ snapshots: dependencies: '@trpc/server': 11.0.0-rc.441 - '@trpc/next@11.0.0-rc.441(@tanstack/react-query@5.76.1(react@19.1.1))(@trpc/client@11.0.0-rc.441(@trpc/server@11.0.0-rc.441))(@trpc/react-query@11.0.0-rc.441(@tanstack/react-query@5.76.1(react@19.1.1))(@trpc/client@11.0.0-rc.441(@trpc/server@11.0.0-rc.441))(@trpc/server@11.0.0-rc.441)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@trpc/server@11.0.0-rc.441)(next@15.5.8(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@trpc/next@11.0.0-rc.441(@tanstack/react-query@5.76.1(react@19.1.1))(@trpc/client@11.0.0-rc.441(@trpc/server@11.0.0-rc.441))(@trpc/react-query@11.0.0-rc.441(@tanstack/react-query@5.76.1(react@19.1.1))(@trpc/client@11.0.0-rc.441(@trpc/server@11.0.0-rc.441))(@trpc/server@11.0.0-rc.441)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@trpc/server@11.0.0-rc.441)(next@15.5.8(@babel/core@7.28.5)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: '@trpc/client': 11.0.0-rc.441(@trpc/server@11.0.0-rc.441) '@trpc/server': 11.0.0-rc.441 - next: 15.5.8(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + next: 15.5.8(@babel/core@7.28.5)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react: 19.1.1 react-dom: 19.1.1(react@19.1.1) optionalDependencies: @@ -14041,15 +14041,6 @@ snapshots: chai: 6.2.1 tinyrainbow: 3.0.3 - '@vitest/mocker@4.0.15(msw@2.10.2(@types/node@22.17.1)(typescript@5.9.3))(vite@6.3.5(@types/node@22.17.1)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.0))': - dependencies: - '@vitest/spy': 4.0.15 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - msw: 2.10.2(@types/node@22.17.1)(typescript@5.9.3) - vite: 6.3.5(@types/node@22.17.1)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.0) - '@vitest/mocker@4.0.15(msw@2.10.2(@types/node@22.17.1)(typescript@5.9.3))(vite@6.3.5(@types/node@22.17.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.3)(yaml@2.8.0))': dependencies: '@vitest/spy': 4.0.15 @@ -15942,7 +15933,7 @@ snapshots: fsevents@2.3.3: optional: true - fumadocs-core@15.7.13(@types/react@19.2.7)(next@16.1.0(@babel/core@7.27.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + fumadocs-core@15.7.13(@types/react@19.2.7)(next@16.1.0(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: '@formatjs/intl-localematcher': 0.6.1 '@orama/orama': 3.1.14 @@ -15963,7 +15954,7 @@ snapshots: unist-util-visit: 5.0.0 optionalDependencies: '@types/react': 19.2.7 - next: 16.1.0(@babel/core@7.27.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + next: 16.1.0(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) transitivePeerDependencies: @@ -15978,7 +15969,7 @@ snapshots: unist-util-visit: 5.0.0 zod: 3.25.76 - fumadocs-mdx@11.6.4(acorn@8.14.1)(fumadocs-core@15.7.13(@types/react@19.2.7)(next@16.1.0(@babel/core@7.27.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(next@16.1.0(@babel/core@7.27.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)): + fumadocs-mdx@11.6.4(acorn@8.14.1)(fumadocs-core@15.7.13(@types/react@19.2.7)(next@16.1.0(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(next@16.1.0(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)): dependencies: '@mdx-js/mdx': 3.1.0(acorn@8.14.1) '@standard-schema/spec': 1.0.0 @@ -15987,11 +15978,11 @@ snapshots: esbuild: 0.25.4 estree-util-value-to-estree: 3.4.0 fast-glob: 3.3.3 - fumadocs-core: 15.7.13(@types/react@19.2.7)(next@16.1.0(@babel/core@7.27.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + fumadocs-core: 15.7.13(@types/react@19.2.7)(next@16.1.0(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) gray-matter: 4.0.3 js-yaml: 4.1.0 lru-cache: 11.1.0 - next: 16.1.0(@babel/core@7.27.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + next: 16.1.0(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) picocolors: 1.1.1 unist-util-visit: 5.0.0 zod: 3.25.76 @@ -15999,11 +15990,11 @@ snapshots: - acorn - supports-color - fumadocs-twoslash@3.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(fumadocs-ui@15.7.13(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(next@16.1.0(@babel/core@7.27.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tailwindcss@4.1.11))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3): + fumadocs-twoslash@3.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(fumadocs-ui@15.7.13(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(next@16.1.0(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tailwindcss@4.1.11))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3): dependencies: '@radix-ui/react-popover': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@shikijs/twoslash': 3.13.0(typescript@5.9.3) - fumadocs-ui: 15.7.13(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(next@16.1.0(@babel/core@7.27.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tailwindcss@4.1.11) + fumadocs-ui: 15.7.13(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(next@16.1.0(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tailwindcss@4.1.11) mdast-util-from-markdown: 2.0.2 mdast-util-gfm: 3.1.0 mdast-util-to-hast: 13.2.0 @@ -16019,10 +16010,10 @@ snapshots: - supports-color - typescript - fumadocs-typescript@4.0.8(@types/react@19.2.7)(fumadocs-core@15.7.13(@types/react@19.2.7)(next@16.1.0(@babel/core@7.27.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(fumadocs-ui@15.7.13(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(next@16.1.0(@babel/core@7.27.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tailwindcss@4.1.11))(typescript@5.9.3): + fumadocs-typescript@4.0.8(@types/react@19.2.7)(fumadocs-core@15.7.13(@types/react@19.2.7)(next@16.1.0(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(fumadocs-ui@15.7.13(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(next@16.1.0(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tailwindcss@4.1.11))(typescript@5.9.3): dependencies: estree-util-value-to-estree: 3.4.0 - fumadocs-core: 15.7.13(@types/react@19.2.7)(next@16.1.0(@babel/core@7.27.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + fumadocs-core: 15.7.13(@types/react@19.2.7)(next@16.1.0(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) hast-util-to-estree: 3.1.3 hast-util-to-jsx-runtime: 2.3.6 remark: 15.0.1 @@ -16033,11 +16024,11 @@ snapshots: unist-util-visit: 5.0.0 optionalDependencies: '@types/react': 19.2.7 - fumadocs-ui: 15.7.13(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(next@16.1.0(@babel/core@7.27.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tailwindcss@4.1.11) + fumadocs-ui: 15.7.13(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(next@16.1.0(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tailwindcss@4.1.11) transitivePeerDependencies: - supports-color - fumadocs-ui@15.7.13(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(next@16.1.0(@babel/core@7.27.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tailwindcss@4.1.11): + fumadocs-ui@15.7.13(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(next@16.1.0(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tailwindcss@4.1.11): dependencies: '@radix-ui/react-accordion': 1.2.12(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -16050,7 +16041,7 @@ snapshots: '@radix-ui/react-slot': 1.2.3(@types/react@19.2.7)(react@19.2.3) '@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) class-variance-authority: 0.7.1 - fumadocs-core: 15.7.13(@types/react@19.2.7)(next@16.1.0(@babel/core@7.27.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + fumadocs-core: 15.7.13(@types/react@19.2.7)(next@16.1.0(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) lodash.merge: 4.6.2 next-themes: 0.4.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) postcss-selector-parser: 7.1.0 @@ -16061,7 +16052,7 @@ snapshots: tailwind-merge: 3.3.1 optionalDependencies: '@types/react': 19.2.7 - next: 16.1.0(@babel/core@7.27.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + next: 16.1.0(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) tailwindcss: 4.1.11 transitivePeerDependencies: - '@mixedbread/sdk' @@ -17638,13 +17629,13 @@ snapshots: optionalDependencies: '@rollup/rollup-linux-x64-gnu': 4.40.2 - next-auth@4.24.11(next@15.5.8(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + next-auth@4.24.11(next@15.5.8(@babel/core@7.28.5)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1): dependencies: '@babel/runtime': 7.27.1 '@panva/hkdf': 1.2.1 cookie: 0.7.2 jose: 4.15.9 - next: 15.5.8(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + next: 15.5.8(@babel/core@7.28.5)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) oauth: 0.9.15 openid-client: 5.7.1 preact: 10.26.6 @@ -17660,7 +17651,7 @@ snapshots: next-tick@1.1.0: {} - next@15.5.8(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + next@15.5.8(@babel/core@7.28.5)(react-dom@19.1.1(react@19.1.1))(react@19.1.1): dependencies: '@next/env': 15.5.8 '@swc/helpers': 0.5.15 @@ -17668,7 +17659,7 @@ snapshots: postcss: 8.4.31 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - styled-jsx: 5.1.6(react@19.1.1) + styled-jsx: 5.1.6(@babel/core@7.28.5)(react@19.1.1) optionalDependencies: '@next/swc-darwin-arm64': 15.5.7 '@next/swc-darwin-x64': 15.5.7 @@ -17707,6 +17698,30 @@ snapshots: - '@babel/core' - babel-plugin-macros + next@16.1.0(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + dependencies: + '@next/env': 16.1.0 + '@swc/helpers': 0.5.15 + baseline-browser-mapping: 2.9.11 + caniuse-lite: 1.0.30001726 + postcss: 8.4.31 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + styled-jsx: 5.1.6(@babel/core@7.28.5)(react@19.2.3) + optionalDependencies: + '@next/swc-darwin-arm64': 16.1.0 + '@next/swc-darwin-x64': 16.1.0 + '@next/swc-linux-arm64-gnu': 16.1.0 + '@next/swc-linux-arm64-musl': 16.1.0 + '@next/swc-linux-x64-gnu': 16.1.0 + '@next/swc-linux-x64-musl': 16.1.0 + '@next/swc-win32-arm64-msvc': 16.1.0 + '@next/swc-win32-x64-msvc': 16.1.0 + sharp: 0.34.5 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + node-abi@3.75.0: dependencies: semver: 7.7.3 @@ -19173,10 +19188,19 @@ snapshots: optionalDependencies: '@babel/core': 7.27.7 - styled-jsx@5.1.6(react@19.1.1): + styled-jsx@5.1.6(@babel/core@7.28.5)(react@19.1.1): dependencies: client-only: 0.0.1 react: 19.1.1 + optionalDependencies: + '@babel/core': 7.28.5 + + styled-jsx@5.1.6(@babel/core@7.28.5)(react@19.2.3): + dependencies: + client-only: 0.0.1 + react: 19.2.3 + optionalDependencies: + '@babel/core': 7.28.5 superjson@2.2.2: dependencies: @@ -19911,7 +19935,7 @@ snapshots: vitest@4.0.15(@types/node@22.17.1)(happy-dom@15.11.7)(jiti@1.21.7)(lightningcss@1.30.2)(msw@2.10.2(@types/node@22.17.1)(typescript@5.9.3))(tsx@4.21.0)(yaml@2.8.0): dependencies: '@vitest/expect': 4.0.15 - '@vitest/mocker': 4.0.15(msw@2.10.2(@types/node@22.17.1)(typescript@5.9.3))(vite@6.3.5(@types/node@22.17.1)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.0)) + '@vitest/mocker': 4.0.15(msw@2.10.2(@types/node@22.17.1)(typescript@5.9.3))(vite@6.3.5(@types/node@22.17.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.0)) '@vitest/pretty-format': 4.0.15 '@vitest/runner': 4.0.15 '@vitest/snapshot': 4.0.15 From 6f5e5f68ed9027cef36ffa7f2f4fe189a8cd82fa Mon Sep 17 00:00:00 2001 From: Eric Luce <37158449+eluce2@users.noreply.github.com> Date: Sat, 20 Dec 2025 07:15:26 -0600 Subject: [PATCH 3/3] fix tests --- packages/typegen/src/typegen.ts | 32 +++++++----- packages/typegen/tests/typegen.test.ts | 71 ++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 14 deletions(-) diff --git a/packages/typegen/src/typegen.ts b/packages/typegen/src/typegen.ts index ff63b191..4ca54bd9 100644 --- a/packages/typegen/src/typegen.ts +++ b/packages/typegen/src/typegen.ts @@ -166,20 +166,24 @@ const generateTypedClientsSingle = async ( webviewerScriptName: config?.type === "fmdapi" ? config.webviewerScriptName : undefined, envNames: { - auth: { - apiKey: - envNames?.auth && "apiKey" in envNames.auth - ? (envNames.auth.apiKey ?? defaultEnvNames.apiKey) - : defaultEnvNames.apiKey, - username: - envNames?.auth && "username" in envNames.auth - ? (envNames.auth.username ?? defaultEnvNames.username) - : defaultEnvNames.username, - password: - envNames?.auth && "password" in envNames.auth - ? (envNames.auth.password ?? defaultEnvNames.password) - : defaultEnvNames.password, - }, + auth: + envNames?.auth && "apiKey" in envNames.auth + ? { + apiKey: envNames.auth.apiKey ?? defaultEnvNames.apiKey, + username: undefined, + password: undefined, + } + : { + apiKey: undefined, + username: + envNames?.auth && "username" in envNames.auth + ? envNames.auth.username ?? defaultEnvNames.username + : defaultEnvNames.username, + password: + envNames?.auth && "password" in envNames.auth + ? envNames.auth.password ?? defaultEnvNames.password + : defaultEnvNames.password, + }, db: envNames?.db ?? defaultEnvNames.db, server: envNames?.server ?? defaultEnvNames.server, }, diff --git a/packages/typegen/tests/typegen.test.ts b/packages/typegen/tests/typegen.test.ts index 67185e24..c9d316c0 100644 --- a/packages/typegen/tests/typegen.test.ts +++ b/packages/typegen/tests/typegen.test.ts @@ -280,4 +280,75 @@ describe("typegen", () => { // Step 3: Clean up generated files await cleanupGeneratedFiles(genPath); }, 30000); + + it("should use OttoAdapter when apiKey is provided in envNames", async () => { + const config: Extract< + z.infer, + { type: "fmdapi" } + > = { + type: "fmdapi", + layouts: [ + { + layoutName: "layout", + schemaName: "testLayout", + generateClient: true, + }, + ], + path: "typegen-output/auth-otto", + envNames: { + auth: { apiKey: "TEST_OTTO_API_KEY" as OttoAPIKey }, + server: "TEST_FM_SERVER", + db: "TEST_FM_DATABASE", + }, + generateClient: true, + }; + + const genPath = await generateTypes(config); + + // Check that the generated client uses OttoAdapter + const clientPath = path.join(genPath, "client", "testLayout.ts"); + const clientContent = await fs.readFile(clientPath, "utf-8"); + + expect(clientContent).toContain("OttoAdapter"); + expect(clientContent).not.toContain("FetchAdapter"); + expect(clientContent).toContain("TEST_OTTO_API_KEY"); + + await cleanupGeneratedFiles(genPath); + }, 30000); + + it("should use FetchAdapter when username/password is provided in envNames", async () => { + const config: Extract< + z.infer, + { type: "fmdapi" } + > = { + type: "fmdapi", + layouts: [ + { + layoutName: "layout", + schemaName: "testLayout", + generateClient: true, + }, + ], + path: "typegen-output/auth-fetch", + envNames: { + auth: { username: "TEST_USERNAME", password: "TEST_PASSWORD" }, + server: "TEST_FM_SERVER", + db: "TEST_FM_DATABASE", + }, + generateClient: true, + }; + + const genPath = await generateTypes(config); + + // Check that the generated client uses FetchAdapter + const clientPath = path.join(genPath, "client", "testLayout.ts"); + const clientContent = await fs.readFile(clientPath, "utf-8"); + + expect(clientContent).toContain("FetchAdapter"); + expect(clientContent).not.toContain("OttoAdapter"); + expect(clientContent).toContain("TEST_USERNAME"); + expect(clientContent).toContain("TEST_PASSWORD"); + + await cleanupGeneratedFiles(genPath); + }, 30000); });