@proofkit/better-auth 0.4.0-beta.4 → 0.4.0-beta.7
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/dist/esm/adapter.d.ts +1 -1
- package/dist/esm/adapter.js +2 -2
- package/dist/esm/adapter.js.map +1 -1
- package/dist/esm/cli/index.js +13 -5
- package/dist/esm/cli/index.js.map +1 -1
- package/package.json +3 -3
- package/src/adapter.ts +2 -2
- package/src/cli/index.ts +14 -5
package/dist/esm/adapter.d.ts
CHANGED
|
@@ -6,4 +6,4 @@ export interface FileMakerAdapterConfig {
|
|
|
6
6
|
database: Database;
|
|
7
7
|
}
|
|
8
8
|
export declare function parseWhere(where?: CleanedWhere[]): string;
|
|
9
|
-
export declare const FileMakerAdapter: (config: FileMakerAdapterConfig) => import('better-auth/adapters').AdapterFactory
|
|
9
|
+
export declare const FileMakerAdapter: (config: FileMakerAdapterConfig) => import('better-auth/adapters').AdapterFactory<import('better-auth').BetterAuthOptions>;
|
package/dist/esm/adapter.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { logger } from "better-auth";
|
|
2
|
-
import {
|
|
2
|
+
import { createAdapterFactory } from "better-auth/adapters";
|
|
3
3
|
const FIELD_SPECIAL_CHARS_REGEX = /[\s_]/;
|
|
4
4
|
const ISO_DATE_REGEX = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z?$/;
|
|
5
5
|
function parseWhere(where) {
|
|
@@ -111,7 +111,7 @@ const FileMakerAdapter = (config) => {
|
|
|
111
111
|
throw new Error("FileMakerAdapter requires a `database` (fmodata Database instance).");
|
|
112
112
|
}
|
|
113
113
|
const db = config.database;
|
|
114
|
-
const adapterFactory =
|
|
114
|
+
const adapterFactory = createAdapterFactory({
|
|
115
115
|
config: {
|
|
116
116
|
adapterId: "filemaker",
|
|
117
117
|
adapterName: "FileMaker",
|
package/dist/esm/adapter.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.js","sources":["../../src/adapter.ts"],"sourcesContent":["/** biome-ignore-all lint/suspicious/noExplicitAny: library code */\nimport type { Database } from \"@proofkit/fmodata\";\nimport { logger } from \"better-auth\";\nimport { type CleanedWhere, createAdapter, type DBAdapterDebugLogOption } from \"better-auth/adapters\";\n\nexport interface FileMakerAdapterConfig {\n /**\n * Helps you debug issues with the adapter.\n */\n debugLogs?: DBAdapterDebugLogOption;\n /**\n * If the table names in the schema are plural.\n */\n usePlural?: boolean;\n /**\n * The fmodata Database instance to use for all OData requests.\n */\n database: Database;\n}\n\n// Regex patterns for field validation and ISO date detection\nconst FIELD_SPECIAL_CHARS_REGEX = /[\\s_]/;\nconst ISO_DATE_REGEX = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{3})?Z?$/;\n\n/**\n * Parse the where clause to an OData filter string.\n * @param where - The where clause to parse.\n * @returns The OData filter string.\n * @internal\n */\nexport function parseWhere(where?: CleanedWhere[]): string {\n if (!where || where.length === 0) {\n return \"\";\n }\n\n // Helper to quote field names with special chars or if field is 'id'\n function quoteField(field: string, value?: any) {\n // Never quote for null or date values (per test expectations)\n if (value === null || value instanceof Date) {\n return field;\n }\n // Always quote if field is 'id' or has space or underscore\n if (field === \"id\" || FIELD_SPECIAL_CHARS_REGEX.test(field)) {\n return `\"${field}\"`;\n }\n return field;\n }\n\n // Helper to format values for OData\n function formatValue(value: any): string {\n if (value === null) {\n return \"null\";\n }\n if (typeof value === \"boolean\") {\n return value ? \"true\" : \"false\";\n }\n if (value instanceof Date) {\n return value.toISOString();\n }\n if (Array.isArray(value)) {\n return `(${value.map(formatValue).join(\",\")})`;\n }\n\n // Handle strings - check if it's an ISO date string first\n if (typeof value === \"string\") {\n // Check if it's an ISO date string (YYYY-MM-DDTHH:mm:ss.sssZ format)\n if (ISO_DATE_REGEX.test(value)) {\n return value; // Return ISO date strings without quotes\n }\n return `'${value.replace(/'/g, \"''\")}'`; // Regular strings get quotes\n }\n\n return value?.toString() ?? \"\";\n }\n\n // Map our operators to OData\n const opMap: Record<string, string> = {\n eq: \"eq\",\n ne: \"ne\",\n lt: \"lt\",\n lte: \"le\",\n gt: \"gt\",\n gte: \"ge\",\n };\n\n // Build each clause\n const clauses: string[] = [];\n for (let i = 0; i < where.length; i++) {\n const cond = where[i];\n if (!cond) {\n continue;\n }\n const field = quoteField(cond.field, cond.value);\n let clause = \"\";\n switch (cond.operator) {\n case \"eq\":\n case \"ne\":\n case \"lt\":\n case \"lte\":\n case \"gt\":\n case \"gte\":\n clause = `${field} ${opMap[cond.operator]} ${formatValue(cond.value)}`;\n break;\n case \"in\":\n if (Array.isArray(cond.value)) {\n clause = cond.value.map((v) => `${field} eq ${formatValue(v)}`).join(\" or \");\n clause = `(${clause})`;\n }\n break;\n case \"contains\":\n clause = `contains(${field}, ${formatValue(cond.value)})`;\n break;\n case \"starts_with\":\n clause = `startswith(${field}, ${formatValue(cond.value)})`;\n break;\n case \"ends_with\":\n clause = `endswith(${field}, ${formatValue(cond.value)})`;\n break;\n default:\n clause = `${field} eq ${formatValue(cond.value)}`;\n }\n clauses.push(clause);\n // Add connector if not last\n if (i < where.length - 1) {\n clauses.push((cond.connector || \"and\").toLowerCase());\n }\n }\n return clauses.join(\" \");\n}\n\n/**\n * Build an OData query string from parameters.\n */\nfunction buildQueryString(params: {\n top?: number;\n skip?: number;\n filter?: string;\n orderBy?: string;\n select?: string[];\n}): string {\n const parts: string[] = [];\n if (params.top !== undefined) {\n parts.push(`$top=${params.top}`);\n }\n if (params.skip !== undefined) {\n parts.push(`$skip=${params.skip}`);\n }\n if (params.filter) {\n parts.push(`$filter=${encodeURIComponent(params.filter)}`);\n }\n if (params.orderBy) {\n parts.push(`$orderby=${encodeURIComponent(params.orderBy)}`);\n }\n if (params.select?.length) {\n parts.push(`$select=${params.select.map(encodeURIComponent).join(\",\")}`);\n }\n return parts.length > 0 ? `?${parts.join(\"&\")}` : \"\";\n}\n\nexport const FileMakerAdapter = (config: FileMakerAdapterConfig) => {\n if (!config.database || typeof config.database !== \"object\") {\n throw new Error(\"FileMakerAdapter requires a `database` (fmodata Database instance).\");\n }\n\n const db = config.database;\n\n const adapterFactory = createAdapter({\n config: {\n adapterId: \"filemaker\",\n adapterName: \"FileMaker\",\n usePlural: config.usePlural ?? false,\n debugLogs: config.debugLogs ?? false,\n supportsJSON: false,\n supportsDates: false,\n supportsBooleans: false,\n supportsNumericIds: false,\n },\n adapter: () => {\n return {\n create: async ({ data, model }) => {\n const result = await db._makeRequest<Record<string, any>>(`/${model}`, {\n method: \"POST\",\n body: JSON.stringify(data),\n });\n\n if (result.error) {\n throw new Error(\"Failed to create record\");\n }\n\n return result.data as any;\n },\n count: async ({ model, where }) => {\n const filter = parseWhere(where);\n logger.debug(\"$filter\", filter);\n\n const query = buildQueryString({\n filter: filter.length > 0 ? filter : undefined,\n });\n\n const result = await db._makeRequest<{ value: number }>(`/${model}/$count${query}`);\n if (result.error) {\n throw new Error(\"Failed to count records\");\n }\n return (result.data?.value as any) ?? 0;\n },\n findOne: async ({ model, where }) => {\n const filter = parseWhere(where);\n logger.debug(\"$filter\", filter);\n\n const query = buildQueryString({\n top: 1,\n filter: filter.length > 0 ? filter : undefined,\n });\n\n const result = await db._makeRequest<{ value: any[] }>(`/${model}${query}`);\n if (result.error) {\n throw new Error(\"Failed to find record\");\n }\n return (result.data?.value?.[0] as any) ?? null;\n },\n findMany: async ({ model, where, limit, offset, sortBy }) => {\n const filter = parseWhere(where);\n logger.debug(\"FIND MANY\", { where, filter });\n\n const query = buildQueryString({\n top: limit,\n skip: offset,\n orderBy: sortBy ? `${sortBy.field} ${sortBy.direction ?? \"asc\"}` : undefined,\n filter: filter.length > 0 ? filter : undefined,\n });\n logger.debug(\"QUERY\", query);\n\n const result = await db._makeRequest<{ value: any[] }>(`/${model}${query}`);\n logger.debug(\"RESULT\", result);\n\n if (result.error) {\n throw new Error(\"Failed to find records\");\n }\n\n return (result.data?.value as any) ?? [];\n },\n delete: async ({ model, where }) => {\n const filter = parseWhere(where);\n logger.debug(\"$filter\", filter);\n\n // Find a single id matching the filter\n const query = buildQueryString({\n top: 1,\n select: [`\"id\"`],\n filter: filter.length > 0 ? filter : undefined,\n });\n\n const toDelete = await db._makeRequest<{ value: { id: string }[] }>(`/${model}${query}`);\n\n const id = toDelete.data?.value?.[0]?.id;\n if (!id) {\n return;\n }\n\n const result = await db._makeRequest(`/${model}('${id}')`, {\n method: \"DELETE\",\n });\n if (result.error) {\n throw new Error(\"Failed to delete record\");\n }\n },\n deleteMany: async ({ model, where }) => {\n const filter = parseWhere(where);\n\n // Find all ids matching the filter\n const query = buildQueryString({\n select: [`\"id\"`],\n filter: filter.length > 0 ? filter : undefined,\n });\n\n const rows = await db._makeRequest<{ value: { id: string }[] }>(`/${model}${query}`);\n\n const ids = rows.data?.value?.map((r: any) => r.id) ?? [];\n let deleted = 0;\n for (const id of ids) {\n const res = await db._makeRequest(`/${model}('${id}')`, {\n method: \"DELETE\",\n });\n if (!res.error) {\n deleted++;\n }\n }\n return deleted;\n },\n update: async ({ model, where, update }) => {\n const filter = parseWhere(where);\n logger.debug(\"UPDATE\", { model, where, update });\n logger.debug(\"$filter\", filter);\n\n // Find one id to update\n const query = buildQueryString({\n select: [`\"id\"`],\n filter: filter.length > 0 ? filter : undefined,\n });\n\n const existing = await db._makeRequest<{ value: { id: string }[] }>(`/${model}${query}`);\n logger.debug(\"EXISTING\", existing.data);\n\n const id = existing.data?.value?.[0]?.id;\n if (!id) {\n return null;\n }\n\n const patchRes = await db._makeRequest(`/${model}('${id}')`, {\n method: \"PATCH\",\n body: JSON.stringify(update),\n });\n logger.debug(\"PATCH RES\", patchRes.data);\n if (patchRes.error) {\n return null;\n }\n\n // Read back the updated record\n const readBack = await db._makeRequest<Record<string, unknown>>(`/${model}('${id}')`);\n logger.debug(\"READ BACK\", readBack.data);\n return (readBack.data as any) ?? null;\n },\n updateMany: async ({ model, where, update }) => {\n const filter = parseWhere(where);\n\n // Find all ids matching the filter\n const query = buildQueryString({\n select: [`\"id\"`],\n filter: filter.length > 0 ? filter : undefined,\n });\n\n const rows = await db._makeRequest<{ value: { id: string }[] }>(`/${model}${query}`);\n\n const ids = rows.data?.value?.map((r: any) => r.id) ?? [];\n let updated = 0;\n for (const id of ids) {\n const res = await db._makeRequest(`/${model}('${id}')`, {\n method: \"PATCH\",\n body: JSON.stringify(update),\n });\n if (!res.error) {\n updated++;\n }\n }\n return updated as any;\n },\n };\n },\n });\n\n // Expose the Database instance for CLI access.\n // Set on both the factory function (for pre-getAdapter extraction)\n // and the returned adapter (for post-getAdapter extraction).\n const originalFactory = adapterFactory;\n const wrappedFactory = ((options: unknown) => {\n const adapter = (originalFactory as (opts: unknown) => Record<string, unknown>)(options);\n adapter.database = db;\n return adapter;\n }) as typeof adapterFactory;\n (wrappedFactory as unknown as { database: Database }).database = db;\n return wrappedFactory;\n};\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"adapter.js","sources":["../../src/adapter.ts"],"sourcesContent":["/** biome-ignore-all lint/suspicious/noExplicitAny: library code */\nimport type { Database } from \"@proofkit/fmodata\";\nimport { logger } from \"better-auth\";\nimport { type CleanedWhere, createAdapterFactory, type DBAdapterDebugLogOption } from \"better-auth/adapters\";\n\nexport interface FileMakerAdapterConfig {\n /**\n * Helps you debug issues with the adapter.\n */\n debugLogs?: DBAdapterDebugLogOption;\n /**\n * If the table names in the schema are plural.\n */\n usePlural?: boolean;\n /**\n * The fmodata Database instance to use for all OData requests.\n */\n database: Database;\n}\n\n// Regex patterns for field validation and ISO date detection\nconst FIELD_SPECIAL_CHARS_REGEX = /[\\s_]/;\nconst ISO_DATE_REGEX = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{3})?Z?$/;\n\n/**\n * Parse the where clause to an OData filter string.\n * @param where - The where clause to parse.\n * @returns The OData filter string.\n * @internal\n */\nexport function parseWhere(where?: CleanedWhere[]): string {\n if (!where || where.length === 0) {\n return \"\";\n }\n\n // Helper to quote field names with special chars or if field is 'id'\n function quoteField(field: string, value?: any) {\n // Never quote for null or date values (per test expectations)\n if (value === null || value instanceof Date) {\n return field;\n }\n // Always quote if field is 'id' or has space or underscore\n if (field === \"id\" || FIELD_SPECIAL_CHARS_REGEX.test(field)) {\n return `\"${field}\"`;\n }\n return field;\n }\n\n // Helper to format values for OData\n function formatValue(value: any): string {\n if (value === null) {\n return \"null\";\n }\n if (typeof value === \"boolean\") {\n return value ? \"true\" : \"false\";\n }\n if (value instanceof Date) {\n return value.toISOString();\n }\n if (Array.isArray(value)) {\n return `(${value.map(formatValue).join(\",\")})`;\n }\n\n // Handle strings - check if it's an ISO date string first\n if (typeof value === \"string\") {\n // Check if it's an ISO date string (YYYY-MM-DDTHH:mm:ss.sssZ format)\n if (ISO_DATE_REGEX.test(value)) {\n return value; // Return ISO date strings without quotes\n }\n return `'${value.replace(/'/g, \"''\")}'`; // Regular strings get quotes\n }\n\n return value?.toString() ?? \"\";\n }\n\n // Map our operators to OData\n const opMap: Record<string, string> = {\n eq: \"eq\",\n ne: \"ne\",\n lt: \"lt\",\n lte: \"le\",\n gt: \"gt\",\n gte: \"ge\",\n };\n\n // Build each clause\n const clauses: string[] = [];\n for (let i = 0; i < where.length; i++) {\n const cond = where[i];\n if (!cond) {\n continue;\n }\n const field = quoteField(cond.field, cond.value);\n let clause = \"\";\n switch (cond.operator) {\n case \"eq\":\n case \"ne\":\n case \"lt\":\n case \"lte\":\n case \"gt\":\n case \"gte\":\n clause = `${field} ${opMap[cond.operator]} ${formatValue(cond.value)}`;\n break;\n case \"in\":\n if (Array.isArray(cond.value)) {\n clause = cond.value.map((v) => `${field} eq ${formatValue(v)}`).join(\" or \");\n clause = `(${clause})`;\n }\n break;\n case \"contains\":\n clause = `contains(${field}, ${formatValue(cond.value)})`;\n break;\n case \"starts_with\":\n clause = `startswith(${field}, ${formatValue(cond.value)})`;\n break;\n case \"ends_with\":\n clause = `endswith(${field}, ${formatValue(cond.value)})`;\n break;\n default:\n clause = `${field} eq ${formatValue(cond.value)}`;\n }\n clauses.push(clause);\n // Add connector if not last\n if (i < where.length - 1) {\n clauses.push((cond.connector || \"and\").toLowerCase());\n }\n }\n return clauses.join(\" \");\n}\n\n/**\n * Build an OData query string from parameters.\n */\nfunction buildQueryString(params: {\n top?: number;\n skip?: number;\n filter?: string;\n orderBy?: string;\n select?: string[];\n}): string {\n const parts: string[] = [];\n if (params.top !== undefined) {\n parts.push(`$top=${params.top}`);\n }\n if (params.skip !== undefined) {\n parts.push(`$skip=${params.skip}`);\n }\n if (params.filter) {\n parts.push(`$filter=${encodeURIComponent(params.filter)}`);\n }\n if (params.orderBy) {\n parts.push(`$orderby=${encodeURIComponent(params.orderBy)}`);\n }\n if (params.select?.length) {\n parts.push(`$select=${params.select.map(encodeURIComponent).join(\",\")}`);\n }\n return parts.length > 0 ? `?${parts.join(\"&\")}` : \"\";\n}\n\nexport const FileMakerAdapter = (config: FileMakerAdapterConfig) => {\n if (!config.database || typeof config.database !== \"object\") {\n throw new Error(\"FileMakerAdapter requires a `database` (fmodata Database instance).\");\n }\n\n const db = config.database;\n\n const adapterFactory = createAdapterFactory({\n config: {\n adapterId: \"filemaker\",\n adapterName: \"FileMaker\",\n usePlural: config.usePlural ?? false,\n debugLogs: config.debugLogs ?? false,\n supportsJSON: false,\n supportsDates: false,\n supportsBooleans: false,\n supportsNumericIds: false,\n },\n adapter: () => {\n return {\n create: async ({ data, model }) => {\n const result = await db._makeRequest<Record<string, any>>(`/${model}`, {\n method: \"POST\",\n body: JSON.stringify(data),\n });\n\n if (result.error) {\n throw new Error(\"Failed to create record\");\n }\n\n return result.data as any;\n },\n count: async ({ model, where }) => {\n const filter = parseWhere(where);\n logger.debug(\"$filter\", filter);\n\n const query = buildQueryString({\n filter: filter.length > 0 ? filter : undefined,\n });\n\n const result = await db._makeRequest<{ value: number }>(`/${model}/$count${query}`);\n if (result.error) {\n throw new Error(\"Failed to count records\");\n }\n return (result.data?.value as any) ?? 0;\n },\n findOne: async ({ model, where }) => {\n const filter = parseWhere(where);\n logger.debug(\"$filter\", filter);\n\n const query = buildQueryString({\n top: 1,\n filter: filter.length > 0 ? filter : undefined,\n });\n\n const result = await db._makeRequest<{ value: any[] }>(`/${model}${query}`);\n if (result.error) {\n throw new Error(\"Failed to find record\");\n }\n return (result.data?.value?.[0] as any) ?? null;\n },\n findMany: async ({ model, where, limit, offset, sortBy }) => {\n const filter = parseWhere(where);\n logger.debug(\"FIND MANY\", { where, filter });\n\n const query = buildQueryString({\n top: limit,\n skip: offset,\n orderBy: sortBy ? `${sortBy.field} ${sortBy.direction ?? \"asc\"}` : undefined,\n filter: filter.length > 0 ? filter : undefined,\n });\n logger.debug(\"QUERY\", query);\n\n const result = await db._makeRequest<{ value: any[] }>(`/${model}${query}`);\n logger.debug(\"RESULT\", result);\n\n if (result.error) {\n throw new Error(\"Failed to find records\");\n }\n\n return (result.data?.value as any) ?? [];\n },\n delete: async ({ model, where }) => {\n const filter = parseWhere(where);\n logger.debug(\"$filter\", filter);\n\n // Find a single id matching the filter\n const query = buildQueryString({\n top: 1,\n select: [`\"id\"`],\n filter: filter.length > 0 ? filter : undefined,\n });\n\n const toDelete = await db._makeRequest<{ value: { id: string }[] }>(`/${model}${query}`);\n\n const id = toDelete.data?.value?.[0]?.id;\n if (!id) {\n return;\n }\n\n const result = await db._makeRequest(`/${model}('${id}')`, {\n method: \"DELETE\",\n });\n if (result.error) {\n throw new Error(\"Failed to delete record\");\n }\n },\n deleteMany: async ({ model, where }) => {\n const filter = parseWhere(where);\n\n // Find all ids matching the filter\n const query = buildQueryString({\n select: [`\"id\"`],\n filter: filter.length > 0 ? filter : undefined,\n });\n\n const rows = await db._makeRequest<{ value: { id: string }[] }>(`/${model}${query}`);\n\n const ids = rows.data?.value?.map((r: any) => r.id) ?? [];\n let deleted = 0;\n for (const id of ids) {\n const res = await db._makeRequest(`/${model}('${id}')`, {\n method: \"DELETE\",\n });\n if (!res.error) {\n deleted++;\n }\n }\n return deleted;\n },\n update: async ({ model, where, update }) => {\n const filter = parseWhere(where);\n logger.debug(\"UPDATE\", { model, where, update });\n logger.debug(\"$filter\", filter);\n\n // Find one id to update\n const query = buildQueryString({\n select: [`\"id\"`],\n filter: filter.length > 0 ? filter : undefined,\n });\n\n const existing = await db._makeRequest<{ value: { id: string }[] }>(`/${model}${query}`);\n logger.debug(\"EXISTING\", existing.data);\n\n const id = existing.data?.value?.[0]?.id;\n if (!id) {\n return null;\n }\n\n const patchRes = await db._makeRequest(`/${model}('${id}')`, {\n method: \"PATCH\",\n body: JSON.stringify(update),\n });\n logger.debug(\"PATCH RES\", patchRes.data);\n if (patchRes.error) {\n return null;\n }\n\n // Read back the updated record\n const readBack = await db._makeRequest<Record<string, unknown>>(`/${model}('${id}')`);\n logger.debug(\"READ BACK\", readBack.data);\n return (readBack.data as any) ?? null;\n },\n updateMany: async ({ model, where, update }) => {\n const filter = parseWhere(where);\n\n // Find all ids matching the filter\n const query = buildQueryString({\n select: [`\"id\"`],\n filter: filter.length > 0 ? filter : undefined,\n });\n\n const rows = await db._makeRequest<{ value: { id: string }[] }>(`/${model}${query}`);\n\n const ids = rows.data?.value?.map((r: any) => r.id) ?? [];\n let updated = 0;\n for (const id of ids) {\n const res = await db._makeRequest(`/${model}('${id}')`, {\n method: \"PATCH\",\n body: JSON.stringify(update),\n });\n if (!res.error) {\n updated++;\n }\n }\n return updated as any;\n },\n };\n },\n });\n\n // Expose the Database instance for CLI access.\n // Set on both the factory function (for pre-getAdapter extraction)\n // and the returned adapter (for post-getAdapter extraction).\n const originalFactory = adapterFactory;\n const wrappedFactory = ((options: unknown) => {\n const adapter = (originalFactory as (opts: unknown) => Record<string, unknown>)(options);\n adapter.database = db;\n return adapter;\n }) as typeof adapterFactory;\n (wrappedFactory as unknown as { database: Database }).database = db;\n return wrappedFactory;\n};\n"],"names":[],"mappings":"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"}
|
package/dist/esm/cli/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { Command } from "@commander-js/extra-typings";
|
|
3
3
|
import { FMServerConnection } from "@proofkit/fmodata";
|
|
4
4
|
import { logger } from "better-auth";
|
|
5
|
-
import {
|
|
5
|
+
import { getSchema } from "better-auth/db";
|
|
6
6
|
import chalk from "chalk";
|
|
7
7
|
import fs from "fs-extra";
|
|
8
8
|
import prompts from "prompts";
|
|
@@ -28,11 +28,19 @@ async function main() {
|
|
|
28
28
|
);
|
|
29
29
|
return;
|
|
30
30
|
}
|
|
31
|
-
const
|
|
32
|
-
|
|
31
|
+
const databaseFactory = config.database;
|
|
32
|
+
if (!databaseFactory || typeof databaseFactory !== "function") {
|
|
33
|
+
logger.error("No database adapter found in auth config.");
|
|
33
34
|
process.exit(1);
|
|
34
|
-
}
|
|
35
|
-
|
|
35
|
+
}
|
|
36
|
+
let adapter;
|
|
37
|
+
try {
|
|
38
|
+
adapter = databaseFactory(config);
|
|
39
|
+
} catch (e) {
|
|
40
|
+
logger.error(e instanceof Error ? e.message : String(e));
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
if ((adapter == null ? void 0 : adapter.id) !== "filemaker") {
|
|
36
44
|
logger.error("This generator is only compatible with the FileMaker adapter.");
|
|
37
45
|
return;
|
|
38
46
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../../src/cli/index.ts"],"sourcesContent":["#!/usr/bin/env node --no-warnings\nimport { Command } from \"@commander-js/extra-typings\";\nimport type { Database, FFetchOptions } from \"@proofkit/fmodata\";\nimport { FMServerConnection } from \"@proofkit/fmodata\";\nimport { logger } from \"better-auth\";\nimport {
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../src/cli/index.ts"],"sourcesContent":["#!/usr/bin/env node --no-warnings\nimport { Command } from \"@commander-js/extra-typings\";\nimport type { Database, FFetchOptions } from \"@proofkit/fmodata\";\nimport { FMServerConnection } from \"@proofkit/fmodata\";\nimport { logger } from \"better-auth\";\nimport { getSchema } from \"better-auth/db\";\nimport chalk from \"chalk\";\nimport fs from \"fs-extra\";\nimport prompts from \"prompts\";\nimport { getConfig } from \"../better-auth-cli/utils/get-config\";\nimport { executeMigration, planMigration, prettyPrintMigrationPlan } from \"../migrate\";\nimport \"dotenv/config\";\n\nasync function main() {\n const program = new Command();\n\n program\n .command(\"migrate\", { isDefault: true })\n .option(\"--cwd <path>\", \"Path to the current working directory\", process.cwd())\n .option(\"--config <path>\", \"Path to the config file\")\n .option(\"-u, --username <username>\", \"Full Access Username\")\n .option(\"-p, --password <password>\", \"Full Access Password\")\n .option(\"-y, --yes\", \"Skip confirmation\", false)\n\n .action(async (options) => {\n const cwd = options.cwd;\n if (!fs.existsSync(cwd)) {\n logger.error(`The directory \"${cwd}\" does not exist.`);\n process.exit(1);\n }\n\n const config = await getConfig({\n cwd,\n configPath: options.config,\n });\n if (!config) {\n logger.error(\n \"No configuration file found. Add a `auth.ts` file to your project or pass the path to the configuration file using the `--config` flag.\",\n );\n return;\n }\n\n // Resolve adapter directly (getAdapter removed in Better Auth 1.5)\n const databaseFactory = config.database;\n if (!databaseFactory || typeof databaseFactory !== \"function\") {\n logger.error(\"No database adapter found in auth config.\");\n process.exit(1);\n }\n let adapter: { id?: string; database?: unknown };\n try {\n adapter = (databaseFactory as (opts: unknown) => { id?: string; database?: unknown })(config);\n } catch (e) {\n logger.error(e instanceof Error ? e.message : String(e));\n process.exit(1);\n }\n\n if (adapter?.id !== \"filemaker\") {\n logger.error(\"This generator is only compatible with the FileMaker adapter.\");\n return;\n }\n\n const betterAuthSchema = getSchema(config);\n\n // Extract Database from the adapter factory or resolved adapter.\n // config.database is the FileMakerAdapter factory function (has .database set on it).\n // adapter is the resolved adapter after getAdapter() calls the factory (also has .database).\n // Try both: adapter first (post-call), then config.database (pre-call / factory function).\n const configDb =\n (adapter as unknown as { database?: Database }).database ??\n (config.database as unknown as { database?: Database } | undefined)?.database;\n if (!configDb || typeof configDb !== \"object\" || !(\"schema\" in configDb)) {\n logger.error(\n \"Could not extract Database instance from adapter. Ensure your auth.ts uses FileMakerAdapter with an fmodata Database.\",\n );\n process.exit(1);\n }\n let db: Database = configDb;\n\n // Extract database name and server URL for display.\n // Try the public getter first (_getDatabaseName), fall back to the private field (databaseName).\n const dbObj = configDb as unknown as {\n _getDatabaseName?: string;\n databaseName?: string;\n context?: { _getBaseUrl?: () => string; _fetchClientOptions?: unknown };\n };\n const dbName: string = dbObj._getDatabaseName ?? dbObj.databaseName ?? \"\";\n const baseUrl: string | undefined = dbObj.context?._getBaseUrl?.();\n const serverUrl = baseUrl ? new URL(baseUrl).origin : undefined;\n\n // If CLI credential overrides are provided, construct a new connection\n if (options.username && options.password) {\n if (!dbName) {\n logger.error(\"Could not determine database filename from adapter config.\");\n process.exit(1);\n }\n\n if (!baseUrl) {\n logger.error(\n \"Could not determine server URL from adapter config. Ensure your auth.ts uses FMServerConnection.\",\n );\n process.exit(1);\n }\n\n const fetchClientOptions = dbObj.context?._fetchClientOptions as FFetchOptions | undefined;\n const connection = new FMServerConnection({\n serverUrl: serverUrl as string,\n auth: {\n username: options.username,\n password: options.password,\n },\n fetchClientOptions,\n });\n\n db = connection.database(dbName);\n }\n\n let migrationPlan: Awaited<ReturnType<typeof planMigration>>;\n try {\n migrationPlan = await planMigration(db, betterAuthSchema);\n } catch (err) {\n logger.error(`Failed to read database schema: ${err instanceof Error ? err.message : err}`);\n process.exit(1);\n }\n\n if (migrationPlan.length === 0) {\n logger.info(\"No changes to apply. Database is up to date.\");\n return;\n }\n\n if (!options.yes) {\n prettyPrintMigrationPlan(migrationPlan, { serverUrl, fileName: dbName });\n\n if (migrationPlan.length > 0) {\n console.log(chalk.gray(\"💡 Tip: You can use the --yes flag to skip this confirmation.\"));\n }\n\n const { confirm } = await prompts({\n type: \"confirm\",\n name: \"confirm\",\n message: \"Apply the above changes to your database?\",\n });\n if (!confirm) {\n logger.error(\"Schema changes not applied.\");\n return;\n }\n }\n\n try {\n await executeMigration(db, migrationPlan);\n logger.info(\"Migration applied successfully.\");\n } catch {\n process.exit(1);\n }\n });\n await program.parseAsync(process.argv);\n process.exit(0);\n}\n\nmain().catch((err) => {\n logger.error(err.message ?? err);\n process.exit(1);\n});\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@proofkit/better-auth",
|
|
3
|
-
"version": "0.4.0-beta.
|
|
3
|
+
"version": "0.4.0-beta.7",
|
|
4
4
|
"description": "FileMaker adapter for Better Auth",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/esm/index.js",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"@babel/preset-typescript": "^7.28.5",
|
|
41
41
|
"@commander-js/extra-typings": "^14.0.0",
|
|
42
42
|
"@tanstack/vite-config": "^0.2.1",
|
|
43
|
-
"better-auth": "^1.4
|
|
43
|
+
"better-auth": "^1.5.4",
|
|
44
44
|
"c12": "^3.3.3",
|
|
45
45
|
"chalk": "5.4.1",
|
|
46
46
|
"commander": "^14.0.2",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"fs-extra": "^11.3.3",
|
|
49
49
|
"prompts": "^2.4.2",
|
|
50
50
|
"vite": "^6.4.1",
|
|
51
|
-
"@proofkit/fmodata": "0.1.0-beta.
|
|
51
|
+
"@proofkit/fmodata": "0.1.0-beta.31"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@types/fs-extra": "^11.0.4",
|
package/src/adapter.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** biome-ignore-all lint/suspicious/noExplicitAny: library code */
|
|
2
2
|
import type { Database } from "@proofkit/fmodata";
|
|
3
3
|
import { logger } from "better-auth";
|
|
4
|
-
import { type CleanedWhere,
|
|
4
|
+
import { type CleanedWhere, createAdapterFactory, type DBAdapterDebugLogOption } from "better-auth/adapters";
|
|
5
5
|
|
|
6
6
|
export interface FileMakerAdapterConfig {
|
|
7
7
|
/**
|
|
@@ -164,7 +164,7 @@ export const FileMakerAdapter = (config: FileMakerAdapterConfig) => {
|
|
|
164
164
|
|
|
165
165
|
const db = config.database;
|
|
166
166
|
|
|
167
|
-
const adapterFactory =
|
|
167
|
+
const adapterFactory = createAdapterFactory({
|
|
168
168
|
config: {
|
|
169
169
|
adapterId: "filemaker",
|
|
170
170
|
adapterName: "FileMaker",
|
package/src/cli/index.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { Command } from "@commander-js/extra-typings";
|
|
|
3
3
|
import type { Database, FFetchOptions } from "@proofkit/fmodata";
|
|
4
4
|
import { FMServerConnection } from "@proofkit/fmodata";
|
|
5
5
|
import { logger } from "better-auth";
|
|
6
|
-
import {
|
|
6
|
+
import { getSchema } from "better-auth/db";
|
|
7
7
|
import chalk from "chalk";
|
|
8
8
|
import fs from "fs-extra";
|
|
9
9
|
import prompts from "prompts";
|
|
@@ -40,12 +40,21 @@ async function main() {
|
|
|
40
40
|
return;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
// Resolve adapter directly (getAdapter removed in Better Auth 1.5)
|
|
44
|
+
const databaseFactory = config.database;
|
|
45
|
+
if (!databaseFactory || typeof databaseFactory !== "function") {
|
|
46
|
+
logger.error("No database adapter found in auth config.");
|
|
45
47
|
process.exit(1);
|
|
46
|
-
}
|
|
48
|
+
}
|
|
49
|
+
let adapter: { id?: string; database?: unknown };
|
|
50
|
+
try {
|
|
51
|
+
adapter = (databaseFactory as (opts: unknown) => { id?: string; database?: unknown })(config);
|
|
52
|
+
} catch (e) {
|
|
53
|
+
logger.error(e instanceof Error ? e.message : String(e));
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
47
56
|
|
|
48
|
-
if (adapter
|
|
57
|
+
if (adapter?.id !== "filemaker") {
|
|
49
58
|
logger.error("This generator is only compatible with the FileMaker adapter.");
|
|
50
59
|
return;
|
|
51
60
|
}
|