@proofkit/better-auth 0.2.4 → 0.3.0
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.js +102 -58
- package/dist/esm/adapter.js.map +1 -1
- package/dist/esm/cli/index.js +5 -3
- package/dist/esm/cli/index.js.map +1 -1
- package/dist/esm/migrate.d.ts +4 -4
- package/dist/esm/migrate.js +22 -3
- package/dist/esm/migrate.js.map +1 -1
- package/dist/esm/odata/index.d.ts +11 -85
- package/dist/esm/odata/index.js +182 -66
- package/dist/esm/odata/index.js.map +1 -1
- package/package.json +3 -3
- package/src/adapter.ts +125 -61
- package/src/cli/index.ts +3 -2
- package/src/migrate.ts +29 -7
- package/src/odata/index.ts +229 -68
package/src/migrate.ts
CHANGED
|
@@ -2,10 +2,10 @@ import { type BetterAuthDbSchema } from "better-auth/db";
|
|
|
2
2
|
import { type Metadata } from "fm-odata-client";
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import z from "zod/v4";
|
|
5
|
-
import {
|
|
5
|
+
import { createRawFetch } from "./odata";
|
|
6
6
|
|
|
7
7
|
export async function getMetadata(
|
|
8
|
-
fetch: ReturnType<typeof
|
|
8
|
+
fetch: ReturnType<typeof createRawFetch>["fetch"],
|
|
9
9
|
databaseName: string,
|
|
10
10
|
) {
|
|
11
11
|
console.log("getting metadata...");
|
|
@@ -21,11 +21,16 @@ export async function getMetadata(
|
|
|
21
21
|
.catch(null),
|
|
22
22
|
});
|
|
23
23
|
|
|
24
|
+
if (result.error) {
|
|
25
|
+
console.error("Failed to get metadata:", result.error);
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
24
29
|
return (result.data?.[databaseName] ?? null) as Metadata | null;
|
|
25
30
|
}
|
|
26
31
|
|
|
27
32
|
export async function planMigration(
|
|
28
|
-
fetch: ReturnType<typeof
|
|
33
|
+
fetch: ReturnType<typeof createRawFetch>["fetch"],
|
|
29
34
|
betterAuthSchema: BetterAuthDbSchema,
|
|
30
35
|
databaseName: string,
|
|
31
36
|
): Promise<MigrationPlan> {
|
|
@@ -156,24 +161,41 @@ export async function planMigration(
|
|
|
156
161
|
}
|
|
157
162
|
|
|
158
163
|
export async function executeMigration(
|
|
159
|
-
fetch: ReturnType<typeof
|
|
164
|
+
fetch: ReturnType<typeof createRawFetch>["fetch"],
|
|
160
165
|
migrationPlan: MigrationPlan,
|
|
161
166
|
) {
|
|
162
167
|
for (const step of migrationPlan) {
|
|
163
168
|
if (step.operation === "create") {
|
|
164
169
|
console.log("Creating table:", step.tableName);
|
|
165
|
-
await fetch("
|
|
170
|
+
const result = await fetch("/FileMaker_Tables", {
|
|
171
|
+
method: "POST",
|
|
166
172
|
body: {
|
|
167
173
|
tableName: step.tableName,
|
|
168
174
|
fields: step.fields,
|
|
169
175
|
},
|
|
170
176
|
});
|
|
177
|
+
|
|
178
|
+
if (result.error) {
|
|
179
|
+
console.error(
|
|
180
|
+
`Failed to create table ${step.tableName}:`,
|
|
181
|
+
result.error,
|
|
182
|
+
);
|
|
183
|
+
throw new Error(`Migration failed: ${result.error}`);
|
|
184
|
+
}
|
|
171
185
|
} else if (step.operation === "update") {
|
|
172
186
|
console.log("Adding fields to table:", step.tableName);
|
|
173
|
-
await fetch(
|
|
174
|
-
|
|
187
|
+
const result = await fetch(`/FileMaker_Tables/${step.tableName}`, {
|
|
188
|
+
method: "PATCH",
|
|
175
189
|
body: { fields: step.fields },
|
|
176
190
|
});
|
|
191
|
+
|
|
192
|
+
if (result.error) {
|
|
193
|
+
console.error(
|
|
194
|
+
`Failed to update table ${step.tableName}:`,
|
|
195
|
+
result.error,
|
|
196
|
+
);
|
|
197
|
+
throw new Error(`Migration failed: ${result.error}`);
|
|
198
|
+
}
|
|
177
199
|
}
|
|
178
200
|
}
|
|
179
201
|
}
|
package/src/odata/index.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { createFetch, createSchema } from "@better-fetch/fetch";
|
|
2
|
-
import { logger } from "@better-fetch/logger";
|
|
3
1
|
import { logger as betterAuthLogger } from "better-auth";
|
|
4
2
|
import { err, ok, Result } from "neverthrow";
|
|
5
3
|
import { z } from "zod/v4";
|
|
@@ -20,83 +18,246 @@ export type FmOdataConfig = {
|
|
|
20
18
|
logging?: true | "verbose" | "none";
|
|
21
19
|
};
|
|
22
20
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
"@patch/FileMaker_Tables/:tableName": {
|
|
34
|
-
params: z.object({ tableName: z.string() }),
|
|
35
|
-
input: z.object({ fields: z.array(z.any()) }),
|
|
36
|
-
},
|
|
37
|
-
/**
|
|
38
|
-
* Delete a table
|
|
39
|
-
*/
|
|
40
|
-
"@delete/FileMaker_Tables/:tableName": {
|
|
41
|
-
params: z.object({ tableName: z.string() }),
|
|
42
|
-
},
|
|
43
|
-
/**
|
|
44
|
-
* Delete a field from a table
|
|
45
|
-
*/
|
|
46
|
-
"@delete/FileMaker_Tables/:tableName/:fieldName": {
|
|
47
|
-
params: z.object({ tableName: z.string(), fieldName: z.string() }),
|
|
48
|
-
},
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
export function createFmOdataFetch(args: FmOdataConfig) {
|
|
21
|
+
export function validateUrl(input: string): Result<URL, unknown> {
|
|
22
|
+
try {
|
|
23
|
+
const url = new URL(input);
|
|
24
|
+
return ok(url);
|
|
25
|
+
} catch (error) {
|
|
26
|
+
return err(error);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function createRawFetch(args: FmOdataConfig) {
|
|
52
31
|
const result = validateUrl(args.serverUrl);
|
|
53
32
|
|
|
54
33
|
if (result.isErr()) {
|
|
55
34
|
throw new Error("Invalid server URL");
|
|
56
35
|
}
|
|
36
|
+
|
|
57
37
|
let baseURL = result.value.origin;
|
|
58
38
|
if ("apiKey" in args.auth) {
|
|
59
39
|
baseURL += `/otto`;
|
|
60
40
|
}
|
|
61
41
|
baseURL += `/fmi/odata/v4/${args.database}`;
|
|
62
42
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
43
|
+
// Create authentication headers
|
|
44
|
+
const authHeaders: Record<string, string> = {};
|
|
45
|
+
if ("apiKey" in args.auth) {
|
|
46
|
+
authHeaders.Authorization = `Bearer ${args.auth.apiKey}`;
|
|
47
|
+
} else {
|
|
48
|
+
const credentials = btoa(`${args.auth.username}:${args.auth.password}`);
|
|
49
|
+
authHeaders.Authorization = `Basic ${credentials}`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Enhanced fetch function with body handling, validation, and structured responses
|
|
53
|
+
const wrappedFetch = async <TOutput = any>(
|
|
54
|
+
input: string | URL | Request,
|
|
55
|
+
options?: Omit<RequestInit, "body"> & {
|
|
56
|
+
body?: any; // Allow any type for body
|
|
57
|
+
output?: z.ZodSchema<TOutput>; // Optional schema for validation
|
|
77
58
|
},
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
verbose: args.logging === "verbose",
|
|
82
|
-
enabled: args.logging === "verbose" || !!args.logging,
|
|
83
|
-
console: {
|
|
84
|
-
fail: (...args) => betterAuthLogger.error("better-fetch", ...args),
|
|
85
|
-
success: (...args) => betterAuthLogger.info("better-fetch", ...args),
|
|
86
|
-
log: (...args) => betterAuthLogger.info("better-fetch", ...args),
|
|
87
|
-
error: (...args) => betterAuthLogger.error("better-fetch", ...args),
|
|
88
|
-
warn: (...args) => betterAuthLogger.warn("better-fetch", ...args),
|
|
89
|
-
},
|
|
90
|
-
}),
|
|
91
|
-
],
|
|
92
|
-
});
|
|
93
|
-
}
|
|
59
|
+
): Promise<{ data?: TOutput; error?: string; response?: Response }> => {
|
|
60
|
+
try {
|
|
61
|
+
let url: string;
|
|
94
62
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
63
|
+
// Handle different input types
|
|
64
|
+
if (typeof input === "string") {
|
|
65
|
+
// If it's already a full URL, use as-is, otherwise prepend baseURL
|
|
66
|
+
url = input.startsWith("http")
|
|
67
|
+
? input
|
|
68
|
+
: `${baseURL}${input.startsWith("/") ? input : `/${input}`}`;
|
|
69
|
+
} else if (input instanceof URL) {
|
|
70
|
+
url = input.toString();
|
|
71
|
+
} else if (input instanceof Request) {
|
|
72
|
+
url = input.url;
|
|
73
|
+
} else {
|
|
74
|
+
url = String(input);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Handle body serialization
|
|
78
|
+
let processedBody = options?.body;
|
|
79
|
+
if (
|
|
80
|
+
processedBody &&
|
|
81
|
+
typeof processedBody === "object" &&
|
|
82
|
+
!(processedBody instanceof FormData) &&
|
|
83
|
+
!(processedBody instanceof URLSearchParams) &&
|
|
84
|
+
!(processedBody instanceof ReadableStream)
|
|
85
|
+
) {
|
|
86
|
+
processedBody = JSON.stringify(processedBody);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Merge headers
|
|
90
|
+
const headers = {
|
|
91
|
+
"Content-Type": "application/json",
|
|
92
|
+
...authHeaders,
|
|
93
|
+
...(options?.headers || {}),
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const requestInit: RequestInit = {
|
|
97
|
+
...options,
|
|
98
|
+
headers,
|
|
99
|
+
body: processedBody,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// Optional logging
|
|
103
|
+
if (args.logging === "verbose" || args.logging === true) {
|
|
104
|
+
betterAuthLogger.info(
|
|
105
|
+
"raw-fetch",
|
|
106
|
+
`${requestInit.method || "GET"} ${url}`,
|
|
107
|
+
);
|
|
108
|
+
if (requestInit.body) {
|
|
109
|
+
betterAuthLogger.info("raw-fetch", "Request body:", requestInit.body);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const response = await fetch(url, requestInit);
|
|
114
|
+
|
|
115
|
+
// Optional logging for response details
|
|
116
|
+
if (args.logging === "verbose" || args.logging === true) {
|
|
117
|
+
betterAuthLogger.info(
|
|
118
|
+
"raw-fetch",
|
|
119
|
+
`Response status: ${response.status} ${response.statusText}`,
|
|
120
|
+
);
|
|
121
|
+
betterAuthLogger.info(
|
|
122
|
+
"raw-fetch",
|
|
123
|
+
`Response headers:`,
|
|
124
|
+
Object.fromEntries(response.headers.entries()),
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Check if response is ok
|
|
129
|
+
if (!response.ok) {
|
|
130
|
+
const errorText = await response.text().catch(() => "Unknown error");
|
|
131
|
+
if (args.logging === "verbose" || args.logging === true) {
|
|
132
|
+
betterAuthLogger.error(
|
|
133
|
+
"raw-fetch",
|
|
134
|
+
`HTTP Error ${response.status}: ${errorText}`,
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
error: `HTTP ${response.status}: ${errorText}`,
|
|
139
|
+
response,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Parse response based on content type
|
|
144
|
+
let responseData: any;
|
|
145
|
+
const contentType = response.headers.get("content-type");
|
|
146
|
+
|
|
147
|
+
if (args.logging === "verbose" || args.logging === true) {
|
|
148
|
+
betterAuthLogger.info(
|
|
149
|
+
"raw-fetch",
|
|
150
|
+
`Response content-type: ${contentType || "none"}`,
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (contentType?.includes("application/json")) {
|
|
155
|
+
try {
|
|
156
|
+
const responseText = await response.text();
|
|
157
|
+
if (args.logging === "verbose" || args.logging === true) {
|
|
158
|
+
betterAuthLogger.info(
|
|
159
|
+
"raw-fetch",
|
|
160
|
+
`Raw response text: "${responseText}"`,
|
|
161
|
+
);
|
|
162
|
+
betterAuthLogger.info(
|
|
163
|
+
"raw-fetch",
|
|
164
|
+
`Response text length: ${responseText.length}`,
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Handle empty responses
|
|
169
|
+
if (responseText.trim() === "") {
|
|
170
|
+
if (args.logging === "verbose" || args.logging === true) {
|
|
171
|
+
betterAuthLogger.info(
|
|
172
|
+
"raw-fetch",
|
|
173
|
+
"Empty JSON response, returning null",
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
responseData = null;
|
|
177
|
+
} else {
|
|
178
|
+
responseData = JSON.parse(responseText);
|
|
179
|
+
if (args.logging === "verbose" || args.logging === true) {
|
|
180
|
+
betterAuthLogger.info(
|
|
181
|
+
"raw-fetch",
|
|
182
|
+
"Successfully parsed JSON response",
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
} catch (parseError) {
|
|
187
|
+
if (args.logging === "verbose" || args.logging === true) {
|
|
188
|
+
betterAuthLogger.error(
|
|
189
|
+
"raw-fetch",
|
|
190
|
+
"JSON parse error:",
|
|
191
|
+
parseError,
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
error: `Failed to parse JSON response: ${parseError instanceof Error ? parseError.message : "Unknown parse error"}`,
|
|
196
|
+
response,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
} else if (contentType?.includes("text/")) {
|
|
200
|
+
// Handle text responses (text/plain, text/html, etc.)
|
|
201
|
+
responseData = await response.text();
|
|
202
|
+
if (args.logging === "verbose" || args.logging === true) {
|
|
203
|
+
betterAuthLogger.info(
|
|
204
|
+
"raw-fetch",
|
|
205
|
+
`Text response: "${responseData}"`,
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
} else {
|
|
209
|
+
// For other content types, try to get text but don't fail if it's binary
|
|
210
|
+
try {
|
|
211
|
+
responseData = await response.text();
|
|
212
|
+
if (args.logging === "verbose" || args.logging === true) {
|
|
213
|
+
betterAuthLogger.info(
|
|
214
|
+
"raw-fetch",
|
|
215
|
+
`Unknown content-type response as text: "${responseData}"`,
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
} catch {
|
|
219
|
+
// If text parsing fails (e.g., binary data), return null
|
|
220
|
+
responseData = null;
|
|
221
|
+
if (args.logging === "verbose" || args.logging === true) {
|
|
222
|
+
betterAuthLogger.info(
|
|
223
|
+
"raw-fetch",
|
|
224
|
+
"Could not parse response as text, returning null",
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Validate output if schema provided
|
|
231
|
+
if (options?.output) {
|
|
232
|
+
const validation = options.output.safeParse(responseData);
|
|
233
|
+
if (validation.success) {
|
|
234
|
+
return {
|
|
235
|
+
data: validation.data,
|
|
236
|
+
response,
|
|
237
|
+
};
|
|
238
|
+
} else {
|
|
239
|
+
return {
|
|
240
|
+
error: `Validation failed: ${validation.error.message}`,
|
|
241
|
+
response,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Return unvalidated data
|
|
247
|
+
return {
|
|
248
|
+
data: responseData as TOutput,
|
|
249
|
+
response,
|
|
250
|
+
};
|
|
251
|
+
} catch (error) {
|
|
252
|
+
return {
|
|
253
|
+
error:
|
|
254
|
+
error instanceof Error ? error.message : "Unknown error occurred",
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
baseURL,
|
|
261
|
+
fetch: wrappedFetch,
|
|
262
|
+
};
|
|
102
263
|
}
|