@proofkit/better-auth 0.3.1-beta.1 → 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.
@@ -1,219 +0,0 @@
1
- /** biome-ignore-all lint/suspicious/noExplicitAny: library code */
2
- import { logger as betterAuthLogger } from "better-auth";
3
- import { err, ok, type Result } from "neverthrow";
4
- import type { z } from "zod/v4";
5
-
6
- interface BasicAuthCredentials {
7
- username: string;
8
- password: string;
9
- }
10
- interface OttoAPIKeyAuth {
11
- apiKey: string;
12
- }
13
- type ODataAuth = BasicAuthCredentials | OttoAPIKeyAuth;
14
-
15
- export interface FmOdataConfig {
16
- serverUrl: string;
17
- auth: ODataAuth;
18
- database: string;
19
- logging?: true | "verbose" | "none";
20
- }
21
-
22
- export function validateUrl(input: string): Result<URL, unknown> {
23
- try {
24
- const url = new URL(input);
25
- return ok(url);
26
- } catch (error) {
27
- return err(error);
28
- }
29
- }
30
-
31
- export function createRawFetch(args: FmOdataConfig) {
32
- const result = validateUrl(args.serverUrl);
33
-
34
- if (result.isErr()) {
35
- throw new Error("Invalid server URL");
36
- }
37
-
38
- let baseURL = result.value.origin;
39
- if ("apiKey" in args.auth) {
40
- baseURL += "/otto";
41
- }
42
- baseURL += `/fmi/odata/v4/${args.database}`;
43
-
44
- // Create authentication headers
45
- const authHeaders: Record<string, string> = {};
46
- if ("apiKey" in args.auth) {
47
- authHeaders.Authorization = `Bearer ${args.auth.apiKey}`;
48
- } else {
49
- const credentials = btoa(`${args.auth.username}:${args.auth.password}`);
50
- authHeaders.Authorization = `Basic ${credentials}`;
51
- }
52
-
53
- // Enhanced fetch function with body handling, validation, and structured responses
54
- const wrappedFetch = async <TOutput = any>(
55
- input: string | URL | Request,
56
- options?: Omit<RequestInit, "body"> & {
57
- body?: any; // Allow any type for body
58
- output?: z.ZodSchema<TOutput>; // Optional schema for validation
59
- },
60
- ): Promise<{ data?: TOutput; error?: string; response?: Response }> => {
61
- try {
62
- let url: string;
63
-
64
- // Handle different input types
65
- if (typeof input === "string") {
66
- // If it's already a full URL, use as-is, otherwise prepend baseURL
67
- url = input.startsWith("http") ? input : `${baseURL}${input.startsWith("/") ? input : `/${input}`}`;
68
- } else if (input instanceof URL) {
69
- url = input.toString();
70
- } else if (input instanceof Request) {
71
- url = input.url;
72
- } else {
73
- url = String(input);
74
- }
75
-
76
- // Handle body serialization
77
- let processedBody = options?.body;
78
- if (
79
- processedBody &&
80
- typeof processedBody === "object" &&
81
- !(processedBody instanceof FormData) &&
82
- !(processedBody instanceof URLSearchParams) &&
83
- !(processedBody instanceof ReadableStream)
84
- ) {
85
- processedBody = JSON.stringify(processedBody);
86
- }
87
-
88
- // Merge headers
89
- const headers = {
90
- "Content-Type": "application/json",
91
- ...authHeaders,
92
- ...(options?.headers || {}),
93
- };
94
-
95
- const requestInit: RequestInit = {
96
- ...options,
97
- headers,
98
- body: processedBody,
99
- };
100
-
101
- // Optional logging
102
- if (args.logging === "verbose" || args.logging === true) {
103
- betterAuthLogger.info("raw-fetch", `${requestInit.method || "GET"} ${url}`);
104
- if (requestInit.body) {
105
- betterAuthLogger.info("raw-fetch", "Request body:", requestInit.body);
106
- }
107
- }
108
-
109
- const response = await fetch(url, requestInit);
110
-
111
- // Optional logging for response details
112
- if (args.logging === "verbose" || args.logging === true) {
113
- betterAuthLogger.info("raw-fetch", `Response status: ${response.status} ${response.statusText}`);
114
- betterAuthLogger.info("raw-fetch", "Response headers:", Object.fromEntries(response.headers.entries()));
115
- }
116
-
117
- // Check if response is ok
118
- if (!response.ok) {
119
- const errorText = await response.text().catch(() => "Unknown error");
120
- if (args.logging === "verbose" || args.logging === true) {
121
- betterAuthLogger.error("raw-fetch", `HTTP Error ${response.status}: ${errorText}`);
122
- }
123
- return {
124
- error: `HTTP ${response.status}: ${errorText}`,
125
- response,
126
- };
127
- }
128
-
129
- // Parse response based on content type
130
- let responseData: any;
131
- const contentType = response.headers.get("content-type");
132
-
133
- if (args.logging === "verbose" || args.logging === true) {
134
- betterAuthLogger.info("raw-fetch", `Response content-type: ${contentType || "none"}`);
135
- }
136
-
137
- if (contentType?.includes("application/json")) {
138
- try {
139
- const responseText = await response.text();
140
- if (args.logging === "verbose" || args.logging === true) {
141
- betterAuthLogger.info("raw-fetch", `Raw response text: "${responseText}"`);
142
- betterAuthLogger.info("raw-fetch", `Response text length: ${responseText.length}`);
143
- }
144
-
145
- // Handle empty responses
146
- if (responseText.trim() === "") {
147
- if (args.logging === "verbose" || args.logging === true) {
148
- betterAuthLogger.info("raw-fetch", "Empty JSON response, returning null");
149
- }
150
- responseData = null;
151
- } else {
152
- responseData = JSON.parse(responseText);
153
- if (args.logging === "verbose" || args.logging === true) {
154
- betterAuthLogger.info("raw-fetch", "Successfully parsed JSON response");
155
- }
156
- }
157
- } catch (parseError) {
158
- if (args.logging === "verbose" || args.logging === true) {
159
- betterAuthLogger.error("raw-fetch", "JSON parse error:", parseError);
160
- }
161
- return {
162
- error: `Failed to parse JSON response: ${parseError instanceof Error ? parseError.message : "Unknown parse error"}`,
163
- response,
164
- };
165
- }
166
- } else if (contentType?.includes("text/")) {
167
- // Handle text responses (text/plain, text/html, etc.)
168
- responseData = await response.text();
169
- if (args.logging === "verbose" || args.logging === true) {
170
- betterAuthLogger.info("raw-fetch", `Text response: "${responseData}"`);
171
- }
172
- } else {
173
- // For other content types, try to get text but don't fail if it's binary
174
- try {
175
- responseData = await response.text();
176
- if (args.logging === "verbose" || args.logging === true) {
177
- betterAuthLogger.info("raw-fetch", `Unknown content-type response as text: "${responseData}"`);
178
- }
179
- } catch {
180
- // If text parsing fails (e.g., binary data), return null
181
- responseData = null;
182
- if (args.logging === "verbose" || args.logging === true) {
183
- betterAuthLogger.info("raw-fetch", "Could not parse response as text, returning null");
184
- }
185
- }
186
- }
187
-
188
- // Validate output if schema provided
189
- if (options?.output) {
190
- const validation = options.output.safeParse(responseData);
191
- if (validation.success) {
192
- return {
193
- data: validation.data,
194
- response,
195
- };
196
- }
197
- return {
198
- error: `Validation failed: ${validation.error.message}`,
199
- response,
200
- };
201
- }
202
-
203
- // Return unvalidated data
204
- return {
205
- data: responseData as TOutput,
206
- response,
207
- };
208
- } catch (error) {
209
- return {
210
- error: error instanceof Error ? error.message : "Unknown error occurred",
211
- };
212
- }
213
- };
214
-
215
- return {
216
- baseURL,
217
- fetch: wrappedFetch,
218
- };
219
- }