@proofkit/better-auth 0.3.0 → 0.3.1-beta.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.d.ts +3 -3
- package/dist/esm/adapter.js +45 -24
- package/dist/esm/adapter.js.map +1 -1
- package/dist/esm/better-auth-cli/utils/add-svelte-kit-env-modules.js +4 -12
- package/dist/esm/better-auth-cli/utils/add-svelte-kit-env-modules.js.map +1 -1
- package/dist/esm/better-auth-cli/utils/get-config.js +12 -21
- package/dist/esm/better-auth-cli/utils/get-config.js.map +1 -1
- package/dist/esm/better-auth-cli/utils/get-tsconfig-info.js +4 -11
- package/dist/esm/better-auth-cli/utils/get-tsconfig-info.js.map +1 -1
- package/dist/esm/cli/index.js +9 -23
- package/dist/esm/cli/index.js.map +1 -1
- package/dist/esm/migrate.d.ts +2 -2
- package/dist/esm/migrate.js +58 -49
- package/dist/esm/migrate.js.map +1 -1
- package/dist/esm/odata/index.d.ts +6 -6
- package/dist/esm/odata/index.js +18 -60
- package/dist/esm/odata/index.js.map +1 -1
- package/package.json +7 -5
- package/src/adapter.ts +55 -41
- package/src/better-auth-cli/utils/add-svelte-kit-env-modules.ts +68 -80
- package/src/better-auth-cli/utils/get-config.ts +16 -30
- package/src/better-auth-cli/utils/get-tsconfig-info.ts +12 -20
- package/src/cli/index.ts +10 -29
- package/src/index.ts +1 -0
- package/src/migrate.ts +68 -77
- package/src/odata/index.ts +29 -73
package/src/migrate.ts
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { type Metadata } from "fm-odata-client";
|
|
1
|
+
import type { BetterAuthDbSchema } from "better-auth/db";
|
|
3
2
|
import chalk from "chalk";
|
|
3
|
+
import type { Metadata } from "fm-odata-client";
|
|
4
4
|
import z from "zod/v4";
|
|
5
|
-
import { createRawFetch } from "./odata";
|
|
5
|
+
import type { createRawFetch } from "./odata";
|
|
6
6
|
|
|
7
|
-
export async function getMetadata(
|
|
8
|
-
fetch: ReturnType<typeof createRawFetch>["fetch"],
|
|
9
|
-
databaseName: string,
|
|
10
|
-
) {
|
|
7
|
+
export async function getMetadata(fetch: ReturnType<typeof createRawFetch>["fetch"], databaseName: string) {
|
|
11
8
|
console.log("getting metadata...");
|
|
12
9
|
const result = await fetch("/$metadata", {
|
|
13
10
|
method: "GET",
|
|
@@ -37,7 +34,7 @@ export async function planMigration(
|
|
|
37
34
|
const metadata = await getMetadata(fetch, databaseName);
|
|
38
35
|
|
|
39
36
|
// Build a map from entity set name to entity type key
|
|
40
|
-
|
|
37
|
+
const entitySetToType: Record<string, string> = {};
|
|
41
38
|
if (metadata) {
|
|
42
39
|
for (const [key, value] of Object.entries(metadata)) {
|
|
43
40
|
if (value.$Kind === "EntitySet" && value.$Type) {
|
|
@@ -52,27 +49,32 @@ export async function planMigration(
|
|
|
52
49
|
? Object.entries(entitySetToType).reduce(
|
|
53
50
|
(acc, [entitySetName, entityTypeKey]) => {
|
|
54
51
|
const entityType = metadata[entityTypeKey];
|
|
55
|
-
if (!entityType)
|
|
52
|
+
if (!entityType) {
|
|
53
|
+
return acc;
|
|
54
|
+
}
|
|
56
55
|
const fields = Object.entries(entityType)
|
|
57
56
|
.filter(
|
|
58
|
-
([
|
|
59
|
-
typeof fieldValue === "object" &&
|
|
60
|
-
fieldValue !== null &&
|
|
61
|
-
"$Type" in fieldValue,
|
|
57
|
+
([_fieldKey, fieldValue]) =>
|
|
58
|
+
typeof fieldValue === "object" && fieldValue !== null && "$Type" in fieldValue,
|
|
62
59
|
)
|
|
63
|
-
.map(([fieldKey, fieldValue]) =>
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
+
});
|
|
76
78
|
acc[entitySetName] = fields;
|
|
77
79
|
return acc;
|
|
78
80
|
},
|
|
@@ -90,42 +92,24 @@ export async function planMigration(
|
|
|
90
92
|
const migrationPlan: MigrationPlan = [];
|
|
91
93
|
|
|
92
94
|
for (const baTable of baTables) {
|
|
93
|
-
const fields: FmField[] = Object.entries(baTable.fields).map(
|
|
94
|
-
|
|
95
|
+
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
|
+
}
|
|
102
|
+
return {
|
|
95
103
|
name: field.fieldName ?? key,
|
|
96
|
-
type
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
: field.type === "date"
|
|
100
|
-
? "timestamp"
|
|
101
|
-
: "varchar",
|
|
102
|
-
}),
|
|
103
|
-
);
|
|
104
|
+
type,
|
|
105
|
+
};
|
|
106
|
+
});
|
|
104
107
|
|
|
105
108
|
// get existing table or create it
|
|
106
|
-
const tableExists =
|
|
107
|
-
existingTables,
|
|
108
|
-
baTable.modelName,
|
|
109
|
-
);
|
|
109
|
+
const tableExists = baTable.modelName in existingTables;
|
|
110
110
|
|
|
111
|
-
if (
|
|
112
|
-
|
|
113
|
-
tableName: baTable.modelName,
|
|
114
|
-
operation: "create",
|
|
115
|
-
fields: [
|
|
116
|
-
{
|
|
117
|
-
name: "id",
|
|
118
|
-
type: "varchar",
|
|
119
|
-
primary: true,
|
|
120
|
-
unique: true,
|
|
121
|
-
},
|
|
122
|
-
...fields,
|
|
123
|
-
],
|
|
124
|
-
});
|
|
125
|
-
} else {
|
|
126
|
-
const existingFields = (existingTables[baTable.modelName] || []).map(
|
|
127
|
-
(f) => f.name,
|
|
128
|
-
);
|
|
111
|
+
if (tableExists) {
|
|
112
|
+
const existingFields = (existingTables[baTable.modelName] || []).map((f) => f.name);
|
|
129
113
|
const existingFieldMap = (existingTables[baTable.modelName] || []).reduce(
|
|
130
114
|
(acc, f) => {
|
|
131
115
|
acc[f.name] = f.type;
|
|
@@ -134,19 +118,14 @@ export async function planMigration(
|
|
|
134
118
|
{} as Record<string, string>,
|
|
135
119
|
);
|
|
136
120
|
// Warn about type mismatches (optional, not in plan)
|
|
137
|
-
|
|
138
|
-
if (
|
|
139
|
-
existingFields.includes(field.name) &&
|
|
140
|
-
existingFieldMap[field.name] !== field.type
|
|
141
|
-
) {
|
|
121
|
+
for (const field of fields) {
|
|
122
|
+
if (existingFields.includes(field.name) && existingFieldMap[field.name] !== field.type) {
|
|
142
123
|
console.warn(
|
|
143
124
|
`⚠️ WARNING: Field '${field.name}' in table '${baTable.modelName}' exists but has type '${existingFieldMap[field.name]}' (expected '${field.type}'). Change the field type in FileMaker to avoid potential errors.`,
|
|
144
125
|
);
|
|
145
126
|
}
|
|
146
|
-
}
|
|
147
|
-
const fieldsToAdd = fields.filter(
|
|
148
|
-
(f) => !existingFields.includes(f.name),
|
|
149
|
-
);
|
|
127
|
+
}
|
|
128
|
+
const fieldsToAdd = fields.filter((f) => !existingFields.includes(f.name));
|
|
150
129
|
if (fieldsToAdd.length > 0) {
|
|
151
130
|
migrationPlan.push({
|
|
152
131
|
tableName: baTable.modelName,
|
|
@@ -154,6 +133,20 @@ export async function planMigration(
|
|
|
154
133
|
fields: fieldsToAdd,
|
|
155
134
|
});
|
|
156
135
|
}
|
|
136
|
+
} else {
|
|
137
|
+
migrationPlan.push({
|
|
138
|
+
tableName: baTable.modelName,
|
|
139
|
+
operation: "create",
|
|
140
|
+
fields: [
|
|
141
|
+
{
|
|
142
|
+
name: "id",
|
|
143
|
+
type: "varchar",
|
|
144
|
+
primary: true,
|
|
145
|
+
unique: true,
|
|
146
|
+
},
|
|
147
|
+
...fields,
|
|
148
|
+
],
|
|
149
|
+
});
|
|
157
150
|
}
|
|
158
151
|
}
|
|
159
152
|
|
|
@@ -176,10 +169,7 @@ export async function executeMigration(
|
|
|
176
169
|
});
|
|
177
170
|
|
|
178
171
|
if (result.error) {
|
|
179
|
-
console.error(
|
|
180
|
-
`Failed to create table ${step.tableName}:`,
|
|
181
|
-
result.error,
|
|
182
|
-
);
|
|
172
|
+
console.error(`Failed to create table ${step.tableName}:`, result.error);
|
|
183
173
|
throw new Error(`Migration failed: ${result.error}`);
|
|
184
174
|
}
|
|
185
175
|
} else if (step.operation === "update") {
|
|
@@ -190,10 +180,7 @@ export async function executeMigration(
|
|
|
190
180
|
});
|
|
191
181
|
|
|
192
182
|
if (result.error) {
|
|
193
|
-
console.error(
|
|
194
|
-
`Failed to update table ${step.tableName}:`,
|
|
195
|
-
result.error,
|
|
196
|
-
);
|
|
183
|
+
console.error(`Failed to update table ${step.tableName}:`, result.error);
|
|
197
184
|
throw new Error(`Migration failed: ${result.error}`);
|
|
198
185
|
}
|
|
199
186
|
}
|
|
@@ -274,8 +261,12 @@ export function prettyPrintMigrationPlan(migrationPlan: MigrationPlan) {
|
|
|
274
261
|
if (step.fields.length) {
|
|
275
262
|
for (const field of step.fields) {
|
|
276
263
|
let fieldDesc = ` - ${field.name} (${field.type}`;
|
|
277
|
-
if (field.primary)
|
|
278
|
-
|
|
264
|
+
if (field.primary) {
|
|
265
|
+
fieldDesc += ", primary";
|
|
266
|
+
}
|
|
267
|
+
if (field.unique) {
|
|
268
|
+
fieldDesc += ", unique";
|
|
269
|
+
}
|
|
279
270
|
fieldDesc += ")";
|
|
280
271
|
console.log(fieldDesc);
|
|
281
272
|
}
|
package/src/odata/index.ts
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
|
+
/** biome-ignore-all lint/suspicious/noExplicitAny: library code */
|
|
1
2
|
import { logger as betterAuthLogger } from "better-auth";
|
|
2
|
-
import { err, ok, Result } from "neverthrow";
|
|
3
|
-
import { z } from "zod/v4";
|
|
3
|
+
import { err, ok, type Result } from "neverthrow";
|
|
4
|
+
import type { z } from "zod/v4";
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
interface BasicAuthCredentials {
|
|
6
7
|
username: string;
|
|
7
8
|
password: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
9
|
+
}
|
|
10
|
+
interface OttoAPIKeyAuth {
|
|
10
11
|
apiKey: string;
|
|
11
|
-
}
|
|
12
|
+
}
|
|
12
13
|
type ODataAuth = BasicAuthCredentials | OttoAPIKeyAuth;
|
|
13
14
|
|
|
14
|
-
export
|
|
15
|
+
export interface FmOdataConfig {
|
|
15
16
|
serverUrl: string;
|
|
16
17
|
auth: ODataAuth;
|
|
17
18
|
database: string;
|
|
18
19
|
logging?: true | "verbose" | "none";
|
|
19
|
-
}
|
|
20
|
+
}
|
|
20
21
|
|
|
21
22
|
export function validateUrl(input: string): Result<URL, unknown> {
|
|
22
23
|
try {
|
|
@@ -36,7 +37,7 @@ export function createRawFetch(args: FmOdataConfig) {
|
|
|
36
37
|
|
|
37
38
|
let baseURL = result.value.origin;
|
|
38
39
|
if ("apiKey" in args.auth) {
|
|
39
|
-
baseURL +=
|
|
40
|
+
baseURL += "/otto";
|
|
40
41
|
}
|
|
41
42
|
baseURL += `/fmi/odata/v4/${args.database}`;
|
|
42
43
|
|
|
@@ -63,9 +64,7 @@ export function createRawFetch(args: FmOdataConfig) {
|
|
|
63
64
|
// Handle different input types
|
|
64
65
|
if (typeof input === "string") {
|
|
65
66
|
// 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}`}`;
|
|
67
|
+
url = input.startsWith("http") ? input : `${baseURL}${input.startsWith("/") ? input : `/${input}`}`;
|
|
69
68
|
} else if (input instanceof URL) {
|
|
70
69
|
url = input.toString();
|
|
71
70
|
} else if (input instanceof Request) {
|
|
@@ -101,10 +100,7 @@ export function createRawFetch(args: FmOdataConfig) {
|
|
|
101
100
|
|
|
102
101
|
// Optional logging
|
|
103
102
|
if (args.logging === "verbose" || args.logging === true) {
|
|
104
|
-
betterAuthLogger.info(
|
|
105
|
-
"raw-fetch",
|
|
106
|
-
`${requestInit.method || "GET"} ${url}`,
|
|
107
|
-
);
|
|
103
|
+
betterAuthLogger.info("raw-fetch", `${requestInit.method || "GET"} ${url}`);
|
|
108
104
|
if (requestInit.body) {
|
|
109
105
|
betterAuthLogger.info("raw-fetch", "Request body:", requestInit.body);
|
|
110
106
|
}
|
|
@@ -114,25 +110,15 @@ export function createRawFetch(args: FmOdataConfig) {
|
|
|
114
110
|
|
|
115
111
|
// Optional logging for response details
|
|
116
112
|
if (args.logging === "verbose" || args.logging === true) {
|
|
117
|
-
betterAuthLogger.info(
|
|
118
|
-
|
|
119
|
-
`Response status: ${response.status} ${response.statusText}`,
|
|
120
|
-
);
|
|
121
|
-
betterAuthLogger.info(
|
|
122
|
-
"raw-fetch",
|
|
123
|
-
`Response headers:`,
|
|
124
|
-
Object.fromEntries(response.headers.entries()),
|
|
125
|
-
);
|
|
113
|
+
betterAuthLogger.info("raw-fetch", `Response status: ${response.status} ${response.statusText}`);
|
|
114
|
+
betterAuthLogger.info("raw-fetch", "Response headers:", Object.fromEntries(response.headers.entries()));
|
|
126
115
|
}
|
|
127
116
|
|
|
128
117
|
// Check if response is ok
|
|
129
118
|
if (!response.ok) {
|
|
130
119
|
const errorText = await response.text().catch(() => "Unknown error");
|
|
131
120
|
if (args.logging === "verbose" || args.logging === true) {
|
|
132
|
-
betterAuthLogger.error(
|
|
133
|
-
"raw-fetch",
|
|
134
|
-
`HTTP Error ${response.status}: ${errorText}`,
|
|
135
|
-
);
|
|
121
|
+
betterAuthLogger.error("raw-fetch", `HTTP Error ${response.status}: ${errorText}`);
|
|
136
122
|
}
|
|
137
123
|
return {
|
|
138
124
|
error: `HTTP ${response.status}: ${errorText}`,
|
|
@@ -145,51 +131,32 @@ export function createRawFetch(args: FmOdataConfig) {
|
|
|
145
131
|
const contentType = response.headers.get("content-type");
|
|
146
132
|
|
|
147
133
|
if (args.logging === "verbose" || args.logging === true) {
|
|
148
|
-
betterAuthLogger.info(
|
|
149
|
-
"raw-fetch",
|
|
150
|
-
`Response content-type: ${contentType || "none"}`,
|
|
151
|
-
);
|
|
134
|
+
betterAuthLogger.info("raw-fetch", `Response content-type: ${contentType || "none"}`);
|
|
152
135
|
}
|
|
153
136
|
|
|
154
137
|
if (contentType?.includes("application/json")) {
|
|
155
138
|
try {
|
|
156
139
|
const responseText = await response.text();
|
|
157
140
|
if (args.logging === "verbose" || args.logging === true) {
|
|
158
|
-
betterAuthLogger.info(
|
|
159
|
-
|
|
160
|
-
`Raw response text: "${responseText}"`,
|
|
161
|
-
);
|
|
162
|
-
betterAuthLogger.info(
|
|
163
|
-
"raw-fetch",
|
|
164
|
-
`Response text length: ${responseText.length}`,
|
|
165
|
-
);
|
|
141
|
+
betterAuthLogger.info("raw-fetch", `Raw response text: "${responseText}"`);
|
|
142
|
+
betterAuthLogger.info("raw-fetch", `Response text length: ${responseText.length}`);
|
|
166
143
|
}
|
|
167
144
|
|
|
168
145
|
// Handle empty responses
|
|
169
146
|
if (responseText.trim() === "") {
|
|
170
147
|
if (args.logging === "verbose" || args.logging === true) {
|
|
171
|
-
betterAuthLogger.info(
|
|
172
|
-
"raw-fetch",
|
|
173
|
-
"Empty JSON response, returning null",
|
|
174
|
-
);
|
|
148
|
+
betterAuthLogger.info("raw-fetch", "Empty JSON response, returning null");
|
|
175
149
|
}
|
|
176
150
|
responseData = null;
|
|
177
151
|
} else {
|
|
178
152
|
responseData = JSON.parse(responseText);
|
|
179
153
|
if (args.logging === "verbose" || args.logging === true) {
|
|
180
|
-
betterAuthLogger.info(
|
|
181
|
-
"raw-fetch",
|
|
182
|
-
"Successfully parsed JSON response",
|
|
183
|
-
);
|
|
154
|
+
betterAuthLogger.info("raw-fetch", "Successfully parsed JSON response");
|
|
184
155
|
}
|
|
185
156
|
}
|
|
186
157
|
} catch (parseError) {
|
|
187
158
|
if (args.logging === "verbose" || args.logging === true) {
|
|
188
|
-
betterAuthLogger.error(
|
|
189
|
-
"raw-fetch",
|
|
190
|
-
"JSON parse error:",
|
|
191
|
-
parseError,
|
|
192
|
-
);
|
|
159
|
+
betterAuthLogger.error("raw-fetch", "JSON parse error:", parseError);
|
|
193
160
|
}
|
|
194
161
|
return {
|
|
195
162
|
error: `Failed to parse JSON response: ${parseError instanceof Error ? parseError.message : "Unknown parse error"}`,
|
|
@@ -200,29 +167,20 @@ export function createRawFetch(args: FmOdataConfig) {
|
|
|
200
167
|
// Handle text responses (text/plain, text/html, etc.)
|
|
201
168
|
responseData = await response.text();
|
|
202
169
|
if (args.logging === "verbose" || args.logging === true) {
|
|
203
|
-
betterAuthLogger.info(
|
|
204
|
-
"raw-fetch",
|
|
205
|
-
`Text response: "${responseData}"`,
|
|
206
|
-
);
|
|
170
|
+
betterAuthLogger.info("raw-fetch", `Text response: "${responseData}"`);
|
|
207
171
|
}
|
|
208
172
|
} else {
|
|
209
173
|
// For other content types, try to get text but don't fail if it's binary
|
|
210
174
|
try {
|
|
211
175
|
responseData = await response.text();
|
|
212
176
|
if (args.logging === "verbose" || args.logging === true) {
|
|
213
|
-
betterAuthLogger.info(
|
|
214
|
-
"raw-fetch",
|
|
215
|
-
`Unknown content-type response as text: "${responseData}"`,
|
|
216
|
-
);
|
|
177
|
+
betterAuthLogger.info("raw-fetch", `Unknown content-type response as text: "${responseData}"`);
|
|
217
178
|
}
|
|
218
179
|
} catch {
|
|
219
180
|
// If text parsing fails (e.g., binary data), return null
|
|
220
181
|
responseData = null;
|
|
221
182
|
if (args.logging === "verbose" || args.logging === true) {
|
|
222
|
-
betterAuthLogger.info(
|
|
223
|
-
"raw-fetch",
|
|
224
|
-
"Could not parse response as text, returning null",
|
|
225
|
-
);
|
|
183
|
+
betterAuthLogger.info("raw-fetch", "Could not parse response as text, returning null");
|
|
226
184
|
}
|
|
227
185
|
}
|
|
228
186
|
}
|
|
@@ -235,12 +193,11 @@ export function createRawFetch(args: FmOdataConfig) {
|
|
|
235
193
|
data: validation.data,
|
|
236
194
|
response,
|
|
237
195
|
};
|
|
238
|
-
} else {
|
|
239
|
-
return {
|
|
240
|
-
error: `Validation failed: ${validation.error.message}`,
|
|
241
|
-
response,
|
|
242
|
-
};
|
|
243
196
|
}
|
|
197
|
+
return {
|
|
198
|
+
error: `Validation failed: ${validation.error.message}`,
|
|
199
|
+
response,
|
|
200
|
+
};
|
|
244
201
|
}
|
|
245
202
|
|
|
246
203
|
// Return unvalidated data
|
|
@@ -250,8 +207,7 @@ export function createRawFetch(args: FmOdataConfig) {
|
|
|
250
207
|
};
|
|
251
208
|
} catch (error) {
|
|
252
209
|
return {
|
|
253
|
-
error:
|
|
254
|
-
error instanceof Error ? error.message : "Unknown error occurred",
|
|
210
|
+
error: error instanceof Error ? error.message : "Unknown error occurred",
|
|
255
211
|
};
|
|
256
212
|
}
|
|
257
213
|
};
|