Skip to content

Commit 06bfdf2

Browse files
authored
Merge pull request #66 from proofgeist/08-20-add_order_by_test
2 parents 9c62bbd + 10f3fc4 commit 06bfdf2

File tree

9 files changed

+569
-201
lines changed

9 files changed

+569
-201
lines changed

.changeset/seven-moose-yell.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@proofkit/better-auth": minor
3+
---
4+
5+
Change underlying fetch implementation

packages/better-auth/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,6 @@
4646
"dependencies": {
4747
"@babel/preset-react": "^7.27.1",
4848
"@babel/preset-typescript": "^7.27.1",
49-
"@better-fetch/fetch": "1.1.17",
50-
"@better-fetch/logger": "^1.1.18",
5149
"@commander-js/extra-typings": "^14.0.0",
5250
"@tanstack/vite-config": "^0.2.0",
5351
"better-auth": "^1.2.10",
@@ -57,13 +55,15 @@
5755
"dotenv": "^16.5.0",
5856
"fs-extra": "^11.3.0",
5957
"neverthrow": "^8.2.0",
58+
"odata-query": "^8.0.4",
6059
"prompts": "^2.4.2",
6160
"vite": "^6.3.4",
6261
"zod": "3.25.64"
6362
},
6463
"devDependencies": {
6564
"@types/fs-extra": "^11.0.4",
6665
"@types/prompts": "^2.4.9",
66+
"@vitest/ui": "^3.2.4",
6767
"fm-odata-client": "^3.0.1",
6868
"publint": "^0.3.12",
6969
"typescript": "^5.9.2",

packages/better-auth/src/adapter.ts

Lines changed: 125 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ import {
33
createAdapter,
44
type AdapterDebugLogs,
55
} from "better-auth/adapters";
6-
import { createFmOdataFetch, type FmOdataConfig } from "./odata";
6+
import { createRawFetch, type FmOdataConfig } from "./odata";
77
import { prettifyError, z } from "zod/v4";
88
import { logger } from "better-auth";
9+
import buildQuery from "odata-query";
910

1011
const configSchema = z.object({
1112
debugLogs: z.unknown().optional(),
@@ -153,7 +154,7 @@ export const FileMakerAdapter = (
153154
}
154155
const config = parsed.data;
155156

156-
const fetch = createFmOdataFetch({
157+
const { fetch, baseURL } = createRawFetch({
157158
...config.odata,
158159
logging: config.debugLogs ? "verbose" : "none",
159160
});
@@ -192,114 +193,177 @@ export const FileMakerAdapter = (
192193
count: async ({ model, where }) => {
193194
const filter = parseWhere(where);
194195
logger.debug("$filter", filter);
195-
const result = await fetch(`/${model}/$count`, {
196+
197+
const query = buildQuery({
198+
filter: filter.length > 0 ? filter : undefined,
199+
});
200+
201+
const result = await fetch(`/${model}/$count${query}`, {
196202
method: "GET",
197-
query: {
198-
$filter: filter,
199-
},
200203
output: z.object({ value: z.number() }),
201204
});
202205
if (!result.data) {
203206
throw new Error("Failed to count records");
204207
}
205-
return result.data?.value ?? 0;
208+
return (result.data?.value as any) ?? 0;
206209
},
207210
findOne: async ({ model, where }) => {
208211
const filter = parseWhere(where);
209212
logger.debug("$filter", filter);
210-
const result = await fetch(`/${model}`, {
213+
214+
const query = buildQuery({
215+
top: 1,
216+
filter: filter.length > 0 ? filter : undefined,
217+
});
218+
219+
const result = await fetch(`/${model}${query}`, {
211220
method: "GET",
212-
query: {
213-
...(filter.length > 0 ? { $filter: filter } : {}),
214-
$top: 1,
215-
},
216221
output: z.object({ value: z.array(z.any()) }),
217222
});
218223
if (result.error) {
219224
throw new Error("Failed to find record");
220225
}
221-
return result.data?.value?.[0] ?? null;
226+
return (result.data?.value?.[0] as any) ?? null;
222227
},
223228
findMany: async ({ model, where, limit, offset, sortBy }) => {
224229
const filter = parseWhere(where);
225-
logger.debug("$filter", filter);
230+
logger.debug("FIND MANY", { where, filter });
231+
232+
const query = buildQuery({
233+
top: limit,
234+
skip: offset,
235+
orderBy: sortBy
236+
? `${sortBy.field} ${sortBy.direction ?? "asc"}`
237+
: undefined,
238+
filter: filter.length > 0 ? filter : undefined,
239+
});
240+
logger.debug("QUERY", query);
226241

227-
const rows = await fetch(`/${model}`, {
242+
const result = await fetch(`/${model}${query}`, {
228243
method: "GET",
229-
query: {
230-
...(filter.length > 0 ? { $filter: filter } : {}),
231-
$top: limit,
232-
$skip: offset,
233-
...(sortBy
234-
? { $orderby: `"${sortBy.field}" ${sortBy.direction ?? "asc"}` }
235-
: {}),
236-
},
237244
output: z.object({ value: z.array(z.any()) }),
238245
});
239-
if (rows.error) {
246+
logger.debug("RESULT", result);
247+
248+
if (result.error) {
240249
throw new Error("Failed to find records");
241250
}
242-
return rows.data?.value ?? [];
251+
252+
return (result.data?.value as any) ?? [];
243253
},
244254
delete: async ({ model, where }) => {
245255
const filter = parseWhere(where);
256+
console.log("DELETE", { model, where, filter });
246257
logger.debug("$filter", filter);
247-
console.log("delete", model, where, filter);
248-
const result = await fetch(`/${model}`, {
258+
259+
// Find a single id matching the filter
260+
const query = buildQuery({
261+
top: 1,
262+
select: [`"id"`],
263+
filter: filter.length > 0 ? filter : undefined,
264+
});
265+
266+
const toDelete = await fetch(`/${model}${query}`, {
267+
method: "GET",
268+
output: z.object({ value: z.array(z.object({ id: z.string() })) }),
269+
});
270+
271+
const id = toDelete.data?.value?.[0]?.id;
272+
if (!id) {
273+
// Nothing to delete
274+
return;
275+
}
276+
277+
const result = await fetch(`/${model}('${id}')`, {
249278
method: "DELETE",
250-
query: {
251-
...(where.length > 0 ? { $filter: filter } : {}),
252-
$top: 1,
253-
},
254279
});
255280
if (result.error) {
281+
console.log("DELETE ERROR", result.error);
256282
throw new Error("Failed to delete record");
257283
}
258284
},
259285
deleteMany: async ({ model, where }) => {
260286
const filter = parseWhere(where);
261-
logger.debug(
262-
where
263-
.map((o) => `typeof ${o.value} is ${typeof o.value}`)
264-
.join("\n"),
265-
);
266-
logger.debug("$filter", filter);
287+
console.log("DELETE MANY", { model, where, filter });
267288

268-
const result = await fetch(`/${model}/$count`, {
269-
method: "DELETE",
270-
query: {
271-
...(where.length > 0 ? { $filter: filter } : {}),
272-
},
273-
output: z.coerce.number(),
289+
// Find all ids matching the filter
290+
const query = buildQuery({
291+
select: [`"id"`],
292+
filter: filter.length > 0 ? filter : undefined,
274293
});
275-
if (result.error) {
276-
throw new Error("Failed to delete record");
294+
295+
const rows = await fetch(`/${model}${query}`, {
296+
method: "GET",
297+
output: z.object({ value: z.array(z.object({ id: z.string() })) }),
298+
});
299+
300+
const ids = rows.data?.value?.map((r: any) => r.id) ?? [];
301+
let deleted = 0;
302+
for (const id of ids) {
303+
const res = await fetch(`/${model}('${id}')`, {
304+
method: "DELETE",
305+
});
306+
if (!res.error) deleted++;
277307
}
278-
return result.data ?? 0;
308+
return deleted;
279309
},
280310
update: async ({ model, where, update }) => {
281-
const result = await fetch(`/${model}`, {
311+
const filter = parseWhere(where);
312+
logger.debug("UPDATE", { model, where, update });
313+
logger.debug("$filter", filter);
314+
// Find one id to update
315+
const query = buildQuery({
316+
select: [`"id"`],
317+
filter: filter.length > 0 ? filter : undefined,
318+
});
319+
320+
const existing = await fetch(`/${model}${query}`, {
321+
method: "GET",
322+
output: z.object({ value: z.array(z.object({ id: z.string() })) }),
323+
});
324+
logger.debug("EXISTING", existing.data);
325+
326+
const id = existing.data?.value?.[0]?.id;
327+
if (!id) return null;
328+
329+
const patchRes = await fetch(`/${model}('${id}')`, {
282330
method: "PATCH",
283-
query: {
284-
...(where.length > 0 ? { $filter: parseWhere(where) } : {}),
285-
$top: 1,
286-
$select: [`"id"`],
287-
},
288331
body: update,
289-
output: z.object({ value: z.array(z.any()) }),
290332
});
291-
return result.data?.value?.[0] ?? null;
333+
logger.debug("PATCH RES", patchRes.data);
334+
if (patchRes.error) return null;
335+
336+
// Read back the updated record
337+
const readBack = await fetch(`/${model}('${id}')`, {
338+
method: "GET",
339+
output: z.record(z.string(), z.unknown()),
340+
});
341+
logger.debug("READ BACK", readBack.data);
342+
return (readBack.data as any) ?? null;
292343
},
293344
updateMany: async ({ model, where, update }) => {
294345
const filter = parseWhere(where);
295-
const result = await fetch(`/${model}`, {
296-
method: "PATCH",
297-
query: {
298-
...(where.length > 0 ? { $filter: filter } : {}),
299-
},
300-
body: update,
346+
// Find all ids matching the filter
347+
const query = buildQuery({
348+
select: [`"id"`],
349+
filter: filter.length > 0 ? filter : undefined,
301350
});
302-
return result.data as any;
351+
352+
const rows = await fetch(`/${model}${query}`, {
353+
method: "GET",
354+
output: z.object({ value: z.array(z.object({ id: z.string() })) }),
355+
});
356+
357+
const ids = rows.data?.value?.map((r: any) => r.id) ?? [];
358+
let updated = 0;
359+
for (const id of ids) {
360+
const res = await fetch(`/${model}('${id}')`, {
361+
method: "PATCH",
362+
body: update,
363+
});
364+
if (!res.error) updated++;
365+
}
366+
return updated as any;
303367
},
304368
};
305369
},

packages/better-auth/src/cli/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { logger } from "better-auth";
1313
import prompts from "prompts";
1414
import chalk from "chalk";
1515
import { AdapterOptions } from "../adapter";
16-
import { createFmOdataFetch } from "../odata";
16+
import { createRawFetch } from "../odata";
1717
import "dotenv/config";
1818

1919
async function main() {
@@ -64,7 +64,7 @@ async function main() {
6464
const betterAuthSchema = getAuthTables(config);
6565

6666
const adapterConfig = (adapter.options as AdapterOptions).config;
67-
const fetch = createFmOdataFetch({
67+
const { fetch } = createRawFetch({
6868
...adapterConfig.odata,
6969
auth:
7070
// If the username and password are provided in the CLI, use them to authenticate instead of what's in the config file.
@@ -74,6 +74,7 @@ async function main() {
7474
password: options.password,
7575
}
7676
: adapterConfig.odata.auth,
77+
logging: "verbose", // Enable logging for CLI operations
7778
});
7879

7980
const migrationPlan = await planMigration(

packages/better-auth/src/migrate.ts

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import { type BetterAuthDbSchema } from "better-auth/db";
22
import { type Metadata } from "fm-odata-client";
33
import chalk from "chalk";
44
import z from "zod/v4";
5-
import { createFmOdataFetch } from "./odata";
5+
import { createRawFetch } from "./odata";
66

77
export async function getMetadata(
8-
fetch: ReturnType<typeof createFmOdataFetch>,
8+
fetch: ReturnType<typeof createRawFetch>["fetch"],
99
databaseName: string,
1010
) {
1111
console.log("getting metadata...");
@@ -21,11 +21,16 @@ export async function getMetadata(
2121
.catch(null),
2222
});
2323

24+
if (result.error) {
25+
console.error("Failed to get metadata:", result.error);
26+
return null;
27+
}
28+
2429
return (result.data?.[databaseName] ?? null) as Metadata | null;
2530
}
2631

2732
export async function planMigration(
28-
fetch: ReturnType<typeof createFmOdataFetch>,
33+
fetch: ReturnType<typeof createRawFetch>["fetch"],
2934
betterAuthSchema: BetterAuthDbSchema,
3035
databaseName: string,
3136
): Promise<MigrationPlan> {
@@ -156,24 +161,41 @@ export async function planMigration(
156161
}
157162

158163
export async function executeMigration(
159-
fetch: ReturnType<typeof createFmOdataFetch>,
164+
fetch: ReturnType<typeof createRawFetch>["fetch"],
160165
migrationPlan: MigrationPlan,
161166
) {
162167
for (const step of migrationPlan) {
163168
if (step.operation === "create") {
164169
console.log("Creating table:", step.tableName);
165-
await fetch("@post/FileMaker_Tables", {
170+
const result = await fetch("/FileMaker_Tables", {
171+
method: "POST",
166172
body: {
167173
tableName: step.tableName,
168174
fields: step.fields,
169175
},
170176
});
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+
}
171185
} else if (step.operation === "update") {
172186
console.log("Adding fields to table:", step.tableName);
173-
await fetch("@post/FileMaker_Tables/:tableName", {
174-
params: { tableName: step.tableName },
187+
const result = await fetch(`/FileMaker_Tables/${step.tableName}`, {
188+
method: "PATCH",
175189
body: { fields: step.fields },
176190
});
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+
}
177199
}
178200
}
179201
}

0 commit comments

Comments
 (0)