@proofkit/better-auth 0.3.1-beta.0 → 0.4.0-beta.2

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/src/migrate.ts CHANGED
@@ -1,111 +1,103 @@
1
- import type { BetterAuthDbSchema } from "better-auth/db";
1
+ import type { Database, Field, Metadata } from "@proofkit/fmodata";
2
+ import { isFMODataError, isODataError } from "@proofkit/fmodata";
3
+ import type { DBFieldAttribute } from "better-auth/db";
2
4
  import chalk from "chalk";
3
- import type { Metadata } from "fm-odata-client";
4
- import z from "zod/v4";
5
- import type { createRawFetch } from "./odata";
6
5
 
7
- export async function getMetadata(fetch: ReturnType<typeof createRawFetch>["fetch"], databaseName: string) {
8
- console.log("getting metadata...");
9
- const result = await fetch("/$metadata", {
10
- method: "GET",
11
- headers: { accept: "application/json" },
12
- output: z
13
- .looseObject({
14
- $Version: z.string(),
15
- "@ServerVersion": z.string(),
16
- })
17
- .or(z.null())
18
- .catch(null),
19
- });
6
+ /** Schema type returned by better-auth's getSchema function */
7
+ type BetterAuthSchema = Record<string, { fields: Record<string, DBFieldAttribute>; order: number }>;
20
8
 
21
- if (result.error) {
22
- console.error("Failed to get metadata:", result.error);
23
- return null;
9
+ function normalizeBetterAuthFieldType(fieldType: unknown): string {
10
+ if (typeof fieldType === "string") {
11
+ return fieldType;
24
12
  }
13
+ if (Array.isArray(fieldType)) {
14
+ return fieldType.map(String).join("|");
15
+ }
16
+ return String(fieldType);
17
+ }
25
18
 
26
- return (result.data?.[databaseName] ?? null) as Metadata | null;
19
+ export async function getMetadata(db: Database): Promise<Metadata> {
20
+ const metadata = await db.getMetadata({ format: "json" });
21
+ return metadata;
22
+ }
23
+
24
+ /** Map a better-auth field type string to an fmodata Field type */
25
+ function mapFieldType(t: string): "string" | "numeric" | "timestamp" {
26
+ if (t.includes("boolean") || t.includes("number")) {
27
+ return "numeric";
28
+ }
29
+ if (t.includes("date")) {
30
+ return "timestamp";
31
+ }
32
+ return "string";
27
33
  }
28
34
 
29
- export async function planMigration(
30
- fetch: ReturnType<typeof createRawFetch>["fetch"],
31
- betterAuthSchema: BetterAuthDbSchema,
32
- databaseName: string,
33
- ): Promise<MigrationPlan> {
34
- const metadata = await getMetadata(fetch, databaseName);
35
+ export async function planMigration(db: Database, betterAuthSchema: BetterAuthSchema): Promise<MigrationPlan> {
36
+ const metadata = await getMetadata(db);
35
37
 
36
38
  // Build a map from entity set name to entity type key
37
39
  const entitySetToType: Record<string, string> = {};
38
- if (metadata) {
39
- for (const [key, value] of Object.entries(metadata)) {
40
- if (value.$Kind === "EntitySet" && value.$Type) {
41
- // $Type is like 'betterauth_test.fmp12.proofkit_user_'
42
- const typeKey = value.$Type.split(".").pop(); // e.g., 'proofkit_user_'
43
- entitySetToType[key] = typeKey || key;
44
- }
40
+ for (const [key, value] of Object.entries(metadata)) {
41
+ if (value.$Kind === "EntitySet" && value.$Type) {
42
+ // $Type is like 'betterauth_test.fmp12.proofkit_user_'
43
+ const typeKey = value.$Type.split(".").pop(); // e.g., 'proofkit_user_'
44
+ entitySetToType[key] = typeKey || key;
45
45
  }
46
46
  }
47
47
 
48
- const existingTables = metadata
49
- ? Object.entries(entitySetToType).reduce(
50
- (acc, [entitySetName, entityTypeKey]) => {
51
- const entityType = metadata[entityTypeKey];
52
- if (!entityType) {
53
- return acc;
48
+ const existingTables = Object.entries(entitySetToType).reduce(
49
+ (acc, [entitySetName, entityTypeKey]) => {
50
+ const entityType = metadata[entityTypeKey];
51
+ if (!entityType) {
52
+ return acc;
53
+ }
54
+ const fields = Object.entries(entityType)
55
+ .filter(
56
+ ([_fieldKey, fieldValue]) => typeof fieldValue === "object" && fieldValue !== null && "$Type" in fieldValue,
57
+ )
58
+ .map(([fieldKey, fieldValue]) => {
59
+ let type = "string";
60
+ if (fieldValue.$Type === "Edm.String") {
61
+ type = "string";
62
+ } else if (fieldValue.$Type === "Edm.DateTimeOffset") {
63
+ type = "timestamp";
64
+ } else if (
65
+ fieldValue.$Type === "Edm.Decimal" ||
66
+ fieldValue.$Type === "Edm.Int32" ||
67
+ fieldValue.$Type === "Edm.Int64"
68
+ ) {
69
+ type = "numeric";
54
70
  }
55
- const fields = Object.entries(entityType)
56
- .filter(
57
- ([_fieldKey, fieldValue]) =>
58
- typeof fieldValue === "object" && fieldValue !== null && "$Type" in fieldValue,
59
- )
60
- .map(([fieldKey, fieldValue]) => {
61
- let type = "varchar";
62
- if (fieldValue.$Type === "Edm.String") {
63
- type = "varchar";
64
- } else if (fieldValue.$Type === "Edm.DateTimeOffset") {
65
- type = "timestamp";
66
- } else if (
67
- fieldValue.$Type === "Edm.Decimal" ||
68
- fieldValue.$Type === "Edm.Int32" ||
69
- fieldValue.$Type === "Edm.Int64"
70
- ) {
71
- type = "numeric";
72
- }
73
- return {
74
- name: fieldKey,
75
- type,
76
- };
77
- });
78
- acc[entitySetName] = fields;
79
- return acc;
80
- },
81
- {} as Record<string, { name: string; type: string }[]>,
82
- )
83
- : {};
71
+ return {
72
+ name: fieldKey,
73
+ type,
74
+ };
75
+ });
76
+ acc[entitySetName] = fields;
77
+ return acc;
78
+ },
79
+ {} as Record<string, { name: string; type: string }[]>,
80
+ );
84
81
 
85
82
  const baTables = Object.entries(betterAuthSchema)
86
83
  .sort((a, b) => (a[1].order ?? 0) - (b[1].order ?? 0))
87
84
  .map(([key, value]) => ({
88
85
  ...value,
89
- keyName: key,
86
+ modelName: key,
90
87
  }));
91
88
 
92
89
  const migrationPlan: MigrationPlan = [];
93
90
 
94
91
  for (const baTable of baTables) {
95
92
  const fields: FmField[] = Object.entries(baTable.fields).map(([key, field]) => {
96
- let type: "varchar" | "numeric" | "timestamp" = "varchar";
97
- if (field.type === "boolean" || field.type.includes("number")) {
98
- type = "numeric";
99
- } else if (field.type === "date") {
100
- type = "timestamp";
101
- }
93
+ const t = normalizeBetterAuthFieldType(field.type);
94
+ const type = mapFieldType(t);
102
95
  return {
103
96
  name: field.fieldName ?? key,
104
97
  type,
105
98
  };
106
99
  });
107
100
 
108
- // get existing table or create it
109
101
  const tableExists = baTable.modelName in existingTables;
110
102
 
111
103
  if (tableExists) {
@@ -117,7 +109,6 @@ export async function planMigration(
117
109
  },
118
110
  {} as Record<string, string>,
119
111
  );
120
- // Warn about type mismatches (optional, not in plan)
121
112
  for (const field of fields) {
122
113
  if (existingFields.includes(field.name) && existingFieldMap[field.name] !== field.type) {
123
114
  console.warn(
@@ -140,7 +131,7 @@ export async function planMigration(
140
131
  fields: [
141
132
  {
142
133
  name: "id",
143
- type: "varchar",
134
+ type: "string",
144
135
  primary: true,
145
136
  unique: true,
146
137
  },
@@ -153,106 +144,101 @@ export async function planMigration(
153
144
  return migrationPlan;
154
145
  }
155
146
 
156
- export async function executeMigration(
157
- fetch: ReturnType<typeof createRawFetch>["fetch"],
158
- migrationPlan: MigrationPlan,
159
- ) {
147
+ export async function executeMigration(db: Database, migrationPlan: MigrationPlan) {
160
148
  for (const step of migrationPlan) {
149
+ // Convert plan fields to fmodata Field type
150
+ const fmodataFields: Field[] = step.fields.map((f) => ({
151
+ name: f.name,
152
+ type: f.type,
153
+ ...(f.primary ? { primary: true } : {}),
154
+ ...(f.unique ? { unique: true } : {}),
155
+ }));
156
+
161
157
  if (step.operation === "create") {
162
158
  console.log("Creating table:", step.tableName);
163
- const result = await fetch("/FileMaker_Tables", {
164
- method: "POST",
165
- body: {
166
- tableName: step.tableName,
167
- fields: step.fields,
168
- },
169
- });
170
-
171
- if (result.error) {
172
- console.error(`Failed to create table ${step.tableName}:`, result.error);
173
- throw new Error(`Migration failed: ${result.error}`);
159
+ try {
160
+ await db.schema.createTable(step.tableName, fmodataFields);
161
+ } catch (error) {
162
+ throw migrationError("create", step.tableName, error);
174
163
  }
175
164
  } else if (step.operation === "update") {
176
165
  console.log("Adding fields to table:", step.tableName);
177
- const result = await fetch(`/FileMaker_Tables/${step.tableName}`, {
178
- method: "PATCH",
179
- body: { fields: step.fields },
180
- });
181
-
182
- if (result.error) {
183
- console.error(`Failed to update table ${step.tableName}:`, result.error);
184
- throw new Error(`Migration failed: ${result.error}`);
166
+ try {
167
+ await db.schema.addFields(step.tableName, fmodataFields);
168
+ } catch (error) {
169
+ throw migrationError("update", step.tableName, error);
185
170
  }
186
171
  }
187
172
  }
188
173
  }
189
174
 
190
- const genericFieldSchema = z.object({
191
- name: z.string(),
192
- nullable: z.boolean().optional(),
193
- primary: z.boolean().optional(),
194
- unique: z.boolean().optional(),
195
- global: z.boolean().optional(),
196
- repetitions: z.number().optional(),
197
- });
198
-
199
- const stringFieldSchema = genericFieldSchema.extend({
200
- type: z.literal("varchar"),
201
- maxLength: z.number().optional(),
202
- default: z.enum(["USER", "USERNAME", "CURRENT_USER"]).optional(),
203
- });
204
-
205
- const numericFieldSchema = genericFieldSchema.extend({
206
- type: z.literal("numeric"),
207
- });
208
-
209
- const dateFieldSchema = genericFieldSchema.extend({
210
- type: z.literal("date"),
211
- default: z.enum(["CURRENT_DATE", "CURDATE"]).optional(),
212
- });
213
-
214
- const timeFieldSchema = genericFieldSchema.extend({
215
- type: z.literal("time"),
216
- default: z.enum(["CURRENT_TIME", "CURTIME"]).optional(),
217
- });
218
-
219
- const timestampFieldSchema = genericFieldSchema.extend({
220
- type: z.literal("timestamp"),
221
- default: z.enum(["CURRENT_TIMESTAMP", "CURTIMESTAMP"]).optional(),
222
- });
175
+ interface FmField {
176
+ name: string;
177
+ type: "string" | "numeric" | "timestamp";
178
+ primary?: boolean;
179
+ unique?: boolean;
180
+ }
223
181
 
224
- const containerFieldSchema = genericFieldSchema.extend({
225
- type: z.literal("container"),
226
- externalSecurePath: z.string().optional(),
227
- });
182
+ const migrationStepTypes = ["create", "update"] as const;
183
+ interface MigrationStep {
184
+ tableName: string;
185
+ operation: (typeof migrationStepTypes)[number];
186
+ fields: FmField[];
187
+ }
228
188
 
229
- const fieldSchema = z.discriminatedUnion("type", [
230
- stringFieldSchema,
231
- numericFieldSchema,
232
- dateFieldSchema,
233
- timeFieldSchema,
234
- timestampFieldSchema,
235
- containerFieldSchema,
236
- ]);
189
+ export type MigrationPlan = MigrationStep[];
237
190
 
238
- type FmField = z.infer<typeof fieldSchema>;
191
+ function formatError(error: unknown): string {
192
+ if (isODataError(error)) {
193
+ const code = error.code ? ` (${error.code})` : "";
194
+ return `${error.message}${code}`;
195
+ }
196
+ if (isFMODataError(error)) {
197
+ return error.message;
198
+ }
199
+ if (error instanceof Error) {
200
+ return error.message;
201
+ }
202
+ return String(error);
203
+ }
239
204
 
240
- const migrationPlanSchema = z
241
- .object({
242
- tableName: z.string(),
243
- operation: z.enum(["create", "update"]),
244
- fields: z.array(fieldSchema),
245
- })
246
- .array();
205
+ function migrationError(operation: string, tableName: string, error: unknown): Error {
206
+ const action = operation === "create" ? "create table" : "update table";
207
+ const base = `Failed to ${action} "${tableName}"`;
247
208
 
248
- export type MigrationPlan = z.infer<typeof migrationPlanSchema>;
209
+ if (isODataError(error) && error.code === "207") {
210
+ console.error(
211
+ chalk.red(`\n${base}: Cannot modify schema.`),
212
+ chalk.yellow("\nThe account used does not have schema modification privileges."),
213
+ chalk.gray(
214
+ "\nUse --username and --password to provide Full Access credentials, or grant schema modification privileges to the current account.",
215
+ ),
216
+ );
217
+ } else {
218
+ console.error(chalk.red(`\n${base}:`), formatError(error));
219
+ }
220
+ return new Error(`Migration failed: ${formatError(error)}`);
221
+ }
249
222
 
250
- export function prettyPrintMigrationPlan(migrationPlan: MigrationPlan) {
223
+ export function prettyPrintMigrationPlan(
224
+ migrationPlan: MigrationPlan,
225
+ target?: { serverUrl?: string; fileName?: string },
226
+ ) {
251
227
  if (!migrationPlan.length) {
252
228
  console.log("No changes to apply. Database is up to date.");
253
229
  return;
254
230
  }
255
231
  console.log(chalk.bold.green("Migration plan:"));
232
+ if (target?.serverUrl || target?.fileName) {
233
+ const parts: string[] = [];
234
+ if (target.fileName) {
235
+ parts.push(chalk.cyan(target.fileName));
236
+ }
237
+ if (target.serverUrl) {
238
+ parts.push(chalk.gray(target.serverUrl));
239
+ }
240
+ console.log(` Target: ${parts.join(" @ ")}`);
241
+ }
256
242
  for (const step of migrationPlan) {
257
243
  const emoji = step.operation === "create" ? "✅" : "✏️";
258
244
  console.log(
@@ -1,29 +0,0 @@
1
- import { Result } from 'neverthrow';
2
- import { z } from 'zod/v4';
3
- interface BasicAuthCredentials {
4
- username: string;
5
- password: string;
6
- }
7
- interface OttoAPIKeyAuth {
8
- apiKey: string;
9
- }
10
- type ODataAuth = BasicAuthCredentials | OttoAPIKeyAuth;
11
- export interface FmOdataConfig {
12
- serverUrl: string;
13
- auth: ODataAuth;
14
- database: string;
15
- logging?: true | "verbose" | "none";
16
- }
17
- export declare function validateUrl(input: string): Result<URL, unknown>;
18
- export declare function createRawFetch(args: FmOdataConfig): {
19
- baseURL: string;
20
- fetch: <TOutput = any>(input: string | URL | Request, options?: Omit<RequestInit, "body"> & {
21
- body?: any;
22
- output?: z.ZodSchema<TOutput>;
23
- }) => Promise<{
24
- data?: TOutput;
25
- error?: string;
26
- response?: Response;
27
- }>;
28
- };
29
- export {};
@@ -1,157 +0,0 @@
1
- import { logger } from "better-auth";
2
- import { ok, err } from "neverthrow";
3
- function validateUrl(input) {
4
- try {
5
- const url = new URL(input);
6
- return ok(url);
7
- } catch (error) {
8
- return err(error);
9
- }
10
- }
11
- function createRawFetch(args) {
12
- const result = validateUrl(args.serverUrl);
13
- if (result.isErr()) {
14
- throw new Error("Invalid server URL");
15
- }
16
- let baseURL = result.value.origin;
17
- if ("apiKey" in args.auth) {
18
- baseURL += "/otto";
19
- }
20
- baseURL += `/fmi/odata/v4/${args.database}`;
21
- const authHeaders = {};
22
- if ("apiKey" in args.auth) {
23
- authHeaders.Authorization = `Bearer ${args.auth.apiKey}`;
24
- } else {
25
- const credentials = btoa(`${args.auth.username}:${args.auth.password}`);
26
- authHeaders.Authorization = `Basic ${credentials}`;
27
- }
28
- const wrappedFetch = async (input, options) => {
29
- try {
30
- let url;
31
- if (typeof input === "string") {
32
- url = input.startsWith("http") ? input : `${baseURL}${input.startsWith("/") ? input : `/${input}`}`;
33
- } else if (input instanceof URL) {
34
- url = input.toString();
35
- } else if (input instanceof Request) {
36
- url = input.url;
37
- } else {
38
- url = String(input);
39
- }
40
- let processedBody = options == null ? void 0 : options.body;
41
- if (processedBody && typeof processedBody === "object" && !(processedBody instanceof FormData) && !(processedBody instanceof URLSearchParams) && !(processedBody instanceof ReadableStream)) {
42
- processedBody = JSON.stringify(processedBody);
43
- }
44
- const headers = {
45
- "Content-Type": "application/json",
46
- ...authHeaders,
47
- ...(options == null ? void 0 : options.headers) || {}
48
- };
49
- const requestInit = {
50
- ...options,
51
- headers,
52
- body: processedBody
53
- };
54
- if (args.logging === "verbose" || args.logging === true) {
55
- logger.info("raw-fetch", `${requestInit.method || "GET"} ${url}`);
56
- if (requestInit.body) {
57
- logger.info("raw-fetch", "Request body:", requestInit.body);
58
- }
59
- }
60
- const response = await fetch(url, requestInit);
61
- if (args.logging === "verbose" || args.logging === true) {
62
- logger.info("raw-fetch", `Response status: ${response.status} ${response.statusText}`);
63
- logger.info("raw-fetch", "Response headers:", Object.fromEntries(response.headers.entries()));
64
- }
65
- if (!response.ok) {
66
- const errorText = await response.text().catch(() => "Unknown error");
67
- if (args.logging === "verbose" || args.logging === true) {
68
- logger.error("raw-fetch", `HTTP Error ${response.status}: ${errorText}`);
69
- }
70
- return {
71
- error: `HTTP ${response.status}: ${errorText}`,
72
- response
73
- };
74
- }
75
- let responseData;
76
- const contentType = response.headers.get("content-type");
77
- if (args.logging === "verbose" || args.logging === true) {
78
- logger.info("raw-fetch", `Response content-type: ${contentType || "none"}`);
79
- }
80
- if (contentType == null ? void 0 : contentType.includes("application/json")) {
81
- try {
82
- const responseText = await response.text();
83
- if (args.logging === "verbose" || args.logging === true) {
84
- logger.info("raw-fetch", `Raw response text: "${responseText}"`);
85
- logger.info("raw-fetch", `Response text length: ${responseText.length}`);
86
- }
87
- if (responseText.trim() === "") {
88
- if (args.logging === "verbose" || args.logging === true) {
89
- logger.info("raw-fetch", "Empty JSON response, returning null");
90
- }
91
- responseData = null;
92
- } else {
93
- responseData = JSON.parse(responseText);
94
- if (args.logging === "verbose" || args.logging === true) {
95
- logger.info("raw-fetch", "Successfully parsed JSON response");
96
- }
97
- }
98
- } catch (parseError) {
99
- if (args.logging === "verbose" || args.logging === true) {
100
- logger.error("raw-fetch", "JSON parse error:", parseError);
101
- }
102
- return {
103
- error: `Failed to parse JSON response: ${parseError instanceof Error ? parseError.message : "Unknown parse error"}`,
104
- response
105
- };
106
- }
107
- } else if (contentType == null ? void 0 : contentType.includes("text/")) {
108
- responseData = await response.text();
109
- if (args.logging === "verbose" || args.logging === true) {
110
- logger.info("raw-fetch", `Text response: "${responseData}"`);
111
- }
112
- } else {
113
- try {
114
- responseData = await response.text();
115
- if (args.logging === "verbose" || args.logging === true) {
116
- logger.info("raw-fetch", `Unknown content-type response as text: "${responseData}"`);
117
- }
118
- } catch {
119
- responseData = null;
120
- if (args.logging === "verbose" || args.logging === true) {
121
- logger.info("raw-fetch", "Could not parse response as text, returning null");
122
- }
123
- }
124
- }
125
- if (options == null ? void 0 : options.output) {
126
- const validation = options.output.safeParse(responseData);
127
- if (validation.success) {
128
- return {
129
- data: validation.data,
130
- response
131
- };
132
- }
133
- return {
134
- error: `Validation failed: ${validation.error.message}`,
135
- response
136
- };
137
- }
138
- return {
139
- data: responseData,
140
- response
141
- };
142
- } catch (error) {
143
- return {
144
- error: error instanceof Error ? error.message : "Unknown error occurred"
145
- };
146
- }
147
- };
148
- return {
149
- baseURL,
150
- fetch: wrappedFetch
151
- };
152
- }
153
- export {
154
- createRawFetch,
155
- validateUrl
156
- };
157
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sources":["../../../src/odata/index.ts"],"sourcesContent":["/** biome-ignore-all lint/suspicious/noExplicitAny: library code */\nimport { logger as betterAuthLogger } from \"better-auth\";\nimport { err, ok, type Result } from \"neverthrow\";\nimport type { z } from \"zod/v4\";\n\ninterface BasicAuthCredentials {\n username: string;\n password: string;\n}\ninterface OttoAPIKeyAuth {\n apiKey: string;\n}\ntype ODataAuth = BasicAuthCredentials | OttoAPIKeyAuth;\n\nexport interface FmOdataConfig {\n serverUrl: string;\n auth: ODataAuth;\n database: string;\n logging?: true | \"verbose\" | \"none\";\n}\n\nexport function validateUrl(input: string): Result<URL, unknown> {\n try {\n const url = new URL(input);\n return ok(url);\n } catch (error) {\n return err(error);\n }\n}\n\nexport function createRawFetch(args: FmOdataConfig) {\n const result = validateUrl(args.serverUrl);\n\n if (result.isErr()) {\n throw new Error(\"Invalid server URL\");\n }\n\n let baseURL = result.value.origin;\n if (\"apiKey\" in args.auth) {\n baseURL += \"/otto\";\n }\n baseURL += `/fmi/odata/v4/${args.database}`;\n\n // Create authentication headers\n const authHeaders: Record<string, string> = {};\n if (\"apiKey\" in args.auth) {\n authHeaders.Authorization = `Bearer ${args.auth.apiKey}`;\n } else {\n const credentials = btoa(`${args.auth.username}:${args.auth.password}`);\n authHeaders.Authorization = `Basic ${credentials}`;\n }\n\n // Enhanced fetch function with body handling, validation, and structured responses\n const wrappedFetch = async <TOutput = any>(\n input: string | URL | Request,\n options?: Omit<RequestInit, \"body\"> & {\n body?: any; // Allow any type for body\n output?: z.ZodSchema<TOutput>; // Optional schema for validation\n },\n ): Promise<{ data?: TOutput; error?: string; response?: Response }> => {\n try {\n let url: string;\n\n // Handle different input types\n if (typeof input === \"string\") {\n // If it's already a full URL, use as-is, otherwise prepend baseURL\n url = input.startsWith(\"http\") ? input : `${baseURL}${input.startsWith(\"/\") ? input : `/${input}`}`;\n } else if (input instanceof URL) {\n url = input.toString();\n } else if (input instanceof Request) {\n url = input.url;\n } else {\n url = String(input);\n }\n\n // Handle body serialization\n let processedBody = options?.body;\n if (\n processedBody &&\n typeof processedBody === \"object\" &&\n !(processedBody instanceof FormData) &&\n !(processedBody instanceof URLSearchParams) &&\n !(processedBody instanceof ReadableStream)\n ) {\n processedBody = JSON.stringify(processedBody);\n }\n\n // Merge headers\n const headers = {\n \"Content-Type\": \"application/json\",\n ...authHeaders,\n ...(options?.headers || {}),\n };\n\n const requestInit: RequestInit = {\n ...options,\n headers,\n body: processedBody,\n };\n\n // Optional logging\n if (args.logging === \"verbose\" || args.logging === true) {\n betterAuthLogger.info(\"raw-fetch\", `${requestInit.method || \"GET\"} ${url}`);\n if (requestInit.body) {\n betterAuthLogger.info(\"raw-fetch\", \"Request body:\", requestInit.body);\n }\n }\n\n const response = await fetch(url, requestInit);\n\n // Optional logging for response details\n if (args.logging === \"verbose\" || args.logging === true) {\n betterAuthLogger.info(\"raw-fetch\", `Response status: ${response.status} ${response.statusText}`);\n betterAuthLogger.info(\"raw-fetch\", \"Response headers:\", Object.fromEntries(response.headers.entries()));\n }\n\n // Check if response is ok\n if (!response.ok) {\n const errorText = await response.text().catch(() => \"Unknown error\");\n if (args.logging === \"verbose\" || args.logging === true) {\n betterAuthLogger.error(\"raw-fetch\", `HTTP Error ${response.status}: ${errorText}`);\n }\n return {\n error: `HTTP ${response.status}: ${errorText}`,\n response,\n };\n }\n\n // Parse response based on content type\n let responseData: any;\n const contentType = response.headers.get(\"content-type\");\n\n if (args.logging === \"verbose\" || args.logging === true) {\n betterAuthLogger.info(\"raw-fetch\", `Response content-type: ${contentType || \"none\"}`);\n }\n\n if (contentType?.includes(\"application/json\")) {\n try {\n const responseText = await response.text();\n if (args.logging === \"verbose\" || args.logging === true) {\n betterAuthLogger.info(\"raw-fetch\", `Raw response text: \"${responseText}\"`);\n betterAuthLogger.info(\"raw-fetch\", `Response text length: ${responseText.length}`);\n }\n\n // Handle empty responses\n if (responseText.trim() === \"\") {\n if (args.logging === \"verbose\" || args.logging === true) {\n betterAuthLogger.info(\"raw-fetch\", \"Empty JSON response, returning null\");\n }\n responseData = null;\n } else {\n responseData = JSON.parse(responseText);\n if (args.logging === \"verbose\" || args.logging === true) {\n betterAuthLogger.info(\"raw-fetch\", \"Successfully parsed JSON response\");\n }\n }\n } catch (parseError) {\n if (args.logging === \"verbose\" || args.logging === true) {\n betterAuthLogger.error(\"raw-fetch\", \"JSON parse error:\", parseError);\n }\n return {\n error: `Failed to parse JSON response: ${parseError instanceof Error ? parseError.message : \"Unknown parse error\"}`,\n response,\n };\n }\n } else if (contentType?.includes(\"text/\")) {\n // Handle text responses (text/plain, text/html, etc.)\n responseData = await response.text();\n if (args.logging === \"verbose\" || args.logging === true) {\n betterAuthLogger.info(\"raw-fetch\", `Text response: \"${responseData}\"`);\n }\n } else {\n // For other content types, try to get text but don't fail if it's binary\n try {\n responseData = await response.text();\n if (args.logging === \"verbose\" || args.logging === true) {\n betterAuthLogger.info(\"raw-fetch\", `Unknown content-type response as text: \"${responseData}\"`);\n }\n } catch {\n // If text parsing fails (e.g., binary data), return null\n responseData = null;\n if (args.logging === \"verbose\" || args.logging === true) {\n betterAuthLogger.info(\"raw-fetch\", \"Could not parse response as text, returning null\");\n }\n }\n }\n\n // Validate output if schema provided\n if (options?.output) {\n const validation = options.output.safeParse(responseData);\n if (validation.success) {\n return {\n data: validation.data,\n response,\n };\n }\n return {\n error: `Validation failed: ${validation.error.message}`,\n response,\n };\n }\n\n // Return unvalidated data\n return {\n data: responseData as TOutput,\n response,\n };\n } catch (error) {\n return {\n error: error instanceof Error ? error.message : \"Unknown error occurred\",\n };\n }\n };\n\n return {\n baseURL,\n fetch: wrappedFetch,\n };\n}\n"],"names":["betterAuthLogger"],"mappings":"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"}