From 51669d8515b0b1b51623398ae07e9d8422440488 Mon Sep 17 00:00:00 2001 From: Eric Luce <37158449+eluce2@users.noreply.github.com> Date: Tue, 3 Feb 2026 14:31:07 -0600 Subject: [PATCH 1/2] ffetch --- packages/better-auth/src/cli/index.ts | 27 ++++-- packages/better-auth/src/migrate.ts | 91 +++++++++---------- .../fmodata/src/client/filemaker-odata.ts | 3 + 3 files changed, 63 insertions(+), 58 deletions(-) diff --git a/packages/better-auth/src/cli/index.ts b/packages/better-auth/src/cli/index.ts index 586737f5..6af37cef 100644 --- a/packages/better-auth/src/cli/index.ts +++ b/packages/better-auth/src/cli/index.ts @@ -1,6 +1,6 @@ #!/usr/bin/env node --no-warnings import { Command } from "@commander-js/extra-typings"; -import type { Database } from "@proofkit/fmodata"; +import type { Database, FFetchOptions } from "@proofkit/fmodata"; import { FMServerConnection } from "@proofkit/fmodata"; import { logger } from "better-auth"; import { getAdapter, getSchema } from "better-auth/db"; @@ -67,12 +67,15 @@ async function main() { } let db: Database = configDb; - // Extract database name and server URL for display - const dbName: string = (configDb as unknown as { _getDatabaseName: string }) - ._getDatabaseName; - const baseUrl: string | undefined = ( - configDb as unknown as { context?: { _getBaseUrl?: () => string } } - ).context?._getBaseUrl?.(); + // Extract database name and server URL for display. + // Try the public getter first (_getDatabaseName), fall back to the private field (databaseName). + const dbObj = configDb as unknown as { + _getDatabaseName?: string; + databaseName?: string; + context?: { _getBaseUrl?: () => string; _fetchClientOptions?: unknown }; + }; + const dbName: string = dbObj._getDatabaseName ?? dbObj.databaseName ?? ""; + const baseUrl: string | undefined = dbObj.context?._getBaseUrl?.(); const serverUrl = baseUrl ? new URL(baseUrl).origin : undefined; // If CLI credential overrides are provided, construct a new connection @@ -89,18 +92,26 @@ async function main() { process.exit(1); } + const fetchClientOptions = dbObj.context?._fetchClientOptions as FFetchOptions | undefined; const connection = new FMServerConnection({ serverUrl: serverUrl as string, auth: { username: options.username, password: options.password, }, + fetchClientOptions, }); db = connection.database(dbName); } - const migrationPlan = await planMigration(db, betterAuthSchema); + let migrationPlan: Awaited>; + try { + migrationPlan = await planMigration(db, betterAuthSchema); + } catch (err) { + logger.error(`Failed to read database schema: ${err instanceof Error ? err.message : err}`); + process.exit(1); + } if (migrationPlan.length === 0) { logger.info("No changes to apply. Database is up to date."); diff --git a/packages/better-auth/src/migrate.ts b/packages/better-auth/src/migrate.ts index 02861fe0..a824e07d 100644 --- a/packages/better-auth/src/migrate.ts +++ b/packages/better-auth/src/migrate.ts @@ -16,14 +16,9 @@ function normalizeBetterAuthFieldType(fieldType: unknown): string { return String(fieldType); } -export async function getMetadata(db: Database): Promise { - try { - const metadata = await db.getMetadata({ format: "json" }); - return metadata; - } catch (err) { - console.error(chalk.red("Failed to get metadata:"), formatError(err)); - return null; - } +export async function getMetadata(db: Database): Promise { + const metadata = await db.getMetadata({ format: "json" }); + return metadata; } /** Map a better-auth field type string to an fmodata Field type */ @@ -42,52 +37,48 @@ export async function planMigration(db: Database, betterAuthSchema: BetterAuthSc // Build a map from entity set name to entity type key const entitySetToType: Record = {}; - if (metadata) { - for (const [key, value] of Object.entries(metadata)) { - if (value.$Kind === "EntitySet" && value.$Type) { - // $Type is like 'betterauth_test.fmp12.proofkit_user_' - const typeKey = value.$Type.split(".").pop(); // e.g., 'proofkit_user_' - entitySetToType[key] = typeKey || key; - } + for (const [key, value] of Object.entries(metadata)) { + if (value.$Kind === "EntitySet" && value.$Type) { + // $Type is like 'betterauth_test.fmp12.proofkit_user_' + const typeKey = value.$Type.split(".").pop(); // e.g., 'proofkit_user_' + entitySetToType[key] = typeKey || key; } } - const existingTables = metadata - ? Object.entries(entitySetToType).reduce( - (acc, [entitySetName, entityTypeKey]) => { - const entityType = metadata[entityTypeKey]; - if (!entityType) { - return acc; + const existingTables = Object.entries(entitySetToType).reduce( + (acc, [entitySetName, entityTypeKey]) => { + const entityType = metadata[entityTypeKey]; + if (!entityType) { + return acc; + } + const fields = Object.entries(entityType) + .filter( + ([_fieldKey, fieldValue]) => + typeof fieldValue === "object" && fieldValue !== null && "$Type" in fieldValue, + ) + .map(([fieldKey, fieldValue]) => { + let type = "string"; + if (fieldValue.$Type === "Edm.String") { + type = "string"; + } else if (fieldValue.$Type === "Edm.DateTimeOffset") { + type = "timestamp"; + } else if ( + fieldValue.$Type === "Edm.Decimal" || + fieldValue.$Type === "Edm.Int32" || + fieldValue.$Type === "Edm.Int64" + ) { + type = "numeric"; } - const fields = Object.entries(entityType) - .filter( - ([_fieldKey, fieldValue]) => - typeof fieldValue === "object" && fieldValue !== null && "$Type" in fieldValue, - ) - .map(([fieldKey, fieldValue]) => { - let type = "string"; - if (fieldValue.$Type === "Edm.String") { - type = "string"; - } else if (fieldValue.$Type === "Edm.DateTimeOffset") { - type = "timestamp"; - } else if ( - fieldValue.$Type === "Edm.Decimal" || - fieldValue.$Type === "Edm.Int32" || - fieldValue.$Type === "Edm.Int64" - ) { - type = "numeric"; - } - return { - name: fieldKey, - type, - }; - }); - acc[entitySetName] = fields; - return acc; - }, - {} as Record, - ) - : {}; + return { + name: fieldKey, + type, + }; + }); + acc[entitySetName] = fields; + return acc; + }, + {} as Record, + ); const baTables = Object.entries(betterAuthSchema) .sort((a, b) => (a[1].order ?? 0) - (b[1].order ?? 0)) diff --git a/packages/fmodata/src/client/filemaker-odata.ts b/packages/fmodata/src/client/filemaker-odata.ts index a2748d2b..45babb2e 100644 --- a/packages/fmodata/src/client/filemaker-odata.ts +++ b/packages/fmodata/src/client/filemaker-odata.ts @@ -23,6 +23,8 @@ export class FMServerConnection implements ExecutionContext { private useEntityIds = false; private includeSpecialColumns = false; private readonly logger: InternalLogger; + /** @internal Stored so credential-override flows can inherit non-auth config. */ + readonly _fetchClientOptions: FFetchOptions | undefined; constructor(config: { serverUrl: string; auth: Auth; @@ -30,6 +32,7 @@ export class FMServerConnection implements ExecutionContext { logger?: Logger; }) { this.logger = createLogger(config.logger); + this._fetchClientOptions = config.fetchClientOptions; this.fetchClient = createClient({ retries: 0, ...config.fetchClientOptions, From 5c2cb4b0c35b622bbd835637e97789eb92d10fdc Mon Sep 17 00:00:00 2001 From: Eric Luce <37158449+eluce2@users.noreply.github.com> Date: Tue, 3 Feb 2026 14:32:32 -0600 Subject: [PATCH 2/2] fix lint --- packages/better-auth/src/migrate.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/better-auth/src/migrate.ts b/packages/better-auth/src/migrate.ts index a824e07d..1fbed672 100644 --- a/packages/better-auth/src/migrate.ts +++ b/packages/better-auth/src/migrate.ts @@ -53,8 +53,7 @@ export async function planMigration(db: Database, betterAuthSchema: BetterAuthSc } const fields = Object.entries(entityType) .filter( - ([_fieldKey, fieldValue]) => - typeof fieldValue === "object" && fieldValue !== null && "$Type" in fieldValue, + ([_fieldKey, fieldValue]) => typeof fieldValue === "object" && fieldValue !== null && "$Type" in fieldValue, ) .map(([fieldKey, fieldValue]) => { let type = "string"; @@ -232,8 +231,12 @@ export function prettyPrintMigrationPlan( console.log(chalk.bold.green("Migration plan:")); if (target?.serverUrl || target?.fileName) { const parts: string[] = []; - if (target.fileName) parts.push(chalk.cyan(target.fileName)); - if (target.serverUrl) parts.push(chalk.gray(target.serverUrl)); + if (target.fileName) { + parts.push(chalk.cyan(target.fileName)); + } + if (target.serverUrl) { + parts.push(chalk.gray(target.serverUrl)); + } console.log(` Target: ${parts.join(" @ ")}`); } for (const step of migrationPlan) {