-
Notifications
You must be signed in to change notification settings - Fork 2
Switch to raw fetch #66
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@proofkit/better-auth": minor | ||
| --- | ||
|
|
||
| Change underlying fetch implementation |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,9 +3,10 @@ import { | |
| createAdapter, | ||
| type AdapterDebugLogs, | ||
| } from "better-auth/adapters"; | ||
| import { createFmOdataFetch, type FmOdataConfig } from "./odata"; | ||
| import { createRawFetch, type FmOdataConfig } from "./odata"; | ||
| import { prettifyError, z } from "zod/v4"; | ||
| import { logger } from "better-auth"; | ||
| import buildQuery from "odata-query"; | ||
|
|
||
| const configSchema = z.object({ | ||
| debugLogs: z.unknown().optional(), | ||
|
|
@@ -153,7 +154,7 @@ export const FileMakerAdapter = ( | |
| } | ||
| const config = parsed.data; | ||
|
|
||
| const fetch = createFmOdataFetch({ | ||
| const { fetch, baseURL } = createRawFetch({ | ||
| ...config.odata, | ||
| logging: config.debugLogs ? "verbose" : "none", | ||
| }); | ||
|
|
@@ -192,114 +193,177 @@ export const FileMakerAdapter = ( | |
| count: async ({ model, where }) => { | ||
| const filter = parseWhere(where); | ||
| logger.debug("$filter", filter); | ||
| const result = await fetch(`/${model}/$count`, { | ||
|
|
||
| const query = buildQuery({ | ||
| filter: filter.length > 0 ? filter : undefined, | ||
| }); | ||
|
|
||
| const result = await fetch(`/${model}/$count${query}`, { | ||
| method: "GET", | ||
| query: { | ||
| $filter: filter, | ||
| }, | ||
| output: z.object({ value: z.number() }), | ||
| }); | ||
| if (!result.data) { | ||
| throw new Error("Failed to count records"); | ||
| } | ||
| return result.data?.value ?? 0; | ||
| return (result.data?.value as any) ?? 0; | ||
| }, | ||
| findOne: async ({ model, where }) => { | ||
| const filter = parseWhere(where); | ||
| logger.debug("$filter", filter); | ||
| const result = await fetch(`/${model}`, { | ||
|
|
||
| const query = buildQuery({ | ||
| top: 1, | ||
| filter: filter.length > 0 ? filter : undefined, | ||
| }); | ||
|
|
||
| const result = await fetch(`/${model}${query}`, { | ||
| method: "GET", | ||
| query: { | ||
| ...(filter.length > 0 ? { $filter: filter } : {}), | ||
| $top: 1, | ||
| }, | ||
| output: z.object({ value: z.array(z.any()) }), | ||
| }); | ||
| if (result.error) { | ||
| throw new Error("Failed to find record"); | ||
| } | ||
| return result.data?.value?.[0] ?? null; | ||
| return (result.data?.value?.[0] as any) ?? null; | ||
| }, | ||
| findMany: async ({ model, where, limit, offset, sortBy }) => { | ||
| const filter = parseWhere(where); | ||
| logger.debug("$filter", filter); | ||
| logger.debug("FIND MANY", { where, filter }); | ||
|
|
||
| const query = buildQuery({ | ||
| top: limit, | ||
| skip: offset, | ||
| orderBy: sortBy | ||
| ? `${sortBy.field} ${sortBy.direction ?? "asc"}` | ||
| : undefined, | ||
| filter: filter.length > 0 ? filter : undefined, | ||
| }); | ||
| logger.debug("QUERY", query); | ||
|
|
||
|
Comment on lines
+232
to
241
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainConfirm odata-query accepts raw string for filter/orderBy. You're passing a prebuilt filter string and a raw 🌐 Web query: 💡 Result: The Details and Examples:
Conclusion:
For reference, see the npm documentation for [ If you need to use a fully raw filter/orderBy string, you must build the full query string manually or use another library that supports direct string injection. Key facts:
[3] Citations:
Use structured The · packages/better-auth/src/adapter.ts (around lines 232–241):
Suggested diff: const query = buildQuery({
top: limit,
skip: offset,
- orderBy: sortBy
- ? `${sortBy.field} ${sortBy.direction ?? "asc"}`
- : undefined,
- filter: filter.length > 0 ? filter : undefined,
+ orderBy: sortBy
+ ? [ { [sortBy.field]: sortBy.direction ?? "asc" } ]
+ : undefined,
+ filter: filter.length > 0
+ ? { and: filter }
+ : undefined,
});
logger.debug("QUERY", query);This ensures your filters and sort orders are serialized properly (e.g.
🤖 Prompt for AI Agents |
||
| const rows = await fetch(`/${model}`, { | ||
| const result = await fetch(`/${model}${query}`, { | ||
| method: "GET", | ||
| query: { | ||
| ...(filter.length > 0 ? { $filter: filter } : {}), | ||
| $top: limit, | ||
| $skip: offset, | ||
| ...(sortBy | ||
| ? { $orderby: `"${sortBy.field}" ${sortBy.direction ?? "asc"}` } | ||
| : {}), | ||
| }, | ||
| output: z.object({ value: z.array(z.any()) }), | ||
| }); | ||
| if (rows.error) { | ||
| logger.debug("RESULT", result); | ||
|
|
||
| if (result.error) { | ||
| throw new Error("Failed to find records"); | ||
| } | ||
| return rows.data?.value ?? []; | ||
|
|
||
| return (result.data?.value as any) ?? []; | ||
| }, | ||
| delete: async ({ model, where }) => { | ||
| const filter = parseWhere(where); | ||
| console.log("DELETE", { model, where, filter }); | ||
| logger.debug("$filter", filter); | ||
| console.log("delete", model, where, filter); | ||
| const result = await fetch(`/${model}`, { | ||
|
|
||
| // Find a single id matching the filter | ||
| const query = buildQuery({ | ||
| top: 1, | ||
| select: [`"id"`], | ||
| filter: filter.length > 0 ? filter : undefined, | ||
| }); | ||
|
|
||
| const toDelete = await fetch(`/${model}${query}`, { | ||
| method: "GET", | ||
| output: z.object({ value: z.array(z.object({ id: z.string() })) }), | ||
| }); | ||
|
|
||
| const id = toDelete.data?.value?.[0]?.id; | ||
| if (!id) { | ||
| // Nothing to delete | ||
| return; | ||
| } | ||
|
|
||
| const result = await fetch(`/${model}('${id}')`, { | ||
| method: "DELETE", | ||
| query: { | ||
| ...(where.length > 0 ? { $filter: filter } : {}), | ||
| $top: 1, | ||
| }, | ||
| }); | ||
| if (result.error) { | ||
| console.log("DELETE ERROR", result.error); | ||
| throw new Error("Failed to delete record"); | ||
| } | ||
| }, | ||
| deleteMany: async ({ model, where }) => { | ||
| const filter = parseWhere(where); | ||
| logger.debug( | ||
| where | ||
| .map((o) => `typeof ${o.value} is ${typeof o.value}`) | ||
| .join("\n"), | ||
| ); | ||
| logger.debug("$filter", filter); | ||
| console.log("DELETE MANY", { model, where, filter }); | ||
|
|
||
| const result = await fetch(`/${model}/$count`, { | ||
| method: "DELETE", | ||
| query: { | ||
| ...(where.length > 0 ? { $filter: filter } : {}), | ||
| }, | ||
| output: z.coerce.number(), | ||
| // Find all ids matching the filter | ||
| const query = buildQuery({ | ||
| select: [`"id"`], | ||
| filter: filter.length > 0 ? filter : undefined, | ||
| }); | ||
| if (result.error) { | ||
| throw new Error("Failed to delete record"); | ||
|
|
||
| const rows = await fetch(`/${model}${query}`, { | ||
| method: "GET", | ||
| output: z.object({ value: z.array(z.object({ id: z.string() })) }), | ||
| }); | ||
|
|
||
| const ids = rows.data?.value?.map((r: any) => r.id) ?? []; | ||
| let deleted = 0; | ||
| for (const id of ids) { | ||
| const res = await fetch(`/${model}('${id}')`, { | ||
| method: "DELETE", | ||
| }); | ||
| if (!res.error) deleted++; | ||
| } | ||
| return result.data ?? 0; | ||
| return deleted; | ||
| }, | ||
| update: async ({ model, where, update }) => { | ||
| const result = await fetch(`/${model}`, { | ||
| const filter = parseWhere(where); | ||
| logger.debug("UPDATE", { model, where, update }); | ||
| logger.debug("$filter", filter); | ||
| // Find one id to update | ||
| const query = buildQuery({ | ||
| select: [`"id"`], | ||
| filter: filter.length > 0 ? filter : undefined, | ||
| }); | ||
|
|
||
| const existing = await fetch(`/${model}${query}`, { | ||
| method: "GET", | ||
| output: z.object({ value: z.array(z.object({ id: z.string() })) }), | ||
| }); | ||
| logger.debug("EXISTING", existing.data); | ||
|
|
||
| const id = existing.data?.value?.[0]?.id; | ||
| if (!id) return null; | ||
|
|
||
| const patchRes = await fetch(`/${model}('${id}')`, { | ||
| method: "PATCH", | ||
| query: { | ||
| ...(where.length > 0 ? { $filter: parseWhere(where) } : {}), | ||
| $top: 1, | ||
| $select: [`"id"`], | ||
| }, | ||
| body: update, | ||
| output: z.object({ value: z.array(z.any()) }), | ||
| }); | ||
| return result.data?.value?.[0] ?? null; | ||
| logger.debug("PATCH RES", patchRes.data); | ||
| if (patchRes.error) return null; | ||
|
|
||
| // Read back the updated record | ||
| const readBack = await fetch(`/${model}('${id}')`, { | ||
| method: "GET", | ||
| output: z.record(z.string(), z.unknown()), | ||
| }); | ||
| logger.debug("READ BACK", readBack.data); | ||
| return (readBack.data as any) ?? null; | ||
| }, | ||
| updateMany: async ({ model, where, update }) => { | ||
| const filter = parseWhere(where); | ||
| const result = await fetch(`/${model}`, { | ||
| method: "PATCH", | ||
| query: { | ||
| ...(where.length > 0 ? { $filter: filter } : {}), | ||
| }, | ||
| body: update, | ||
| // Find all ids matching the filter | ||
| const query = buildQuery({ | ||
| select: [`"id"`], | ||
| filter: filter.length > 0 ? filter : undefined, | ||
| }); | ||
| return result.data as any; | ||
|
|
||
| const rows = await fetch(`/${model}${query}`, { | ||
| method: "GET", | ||
| output: z.object({ value: z.array(z.object({ id: z.string() })) }), | ||
| }); | ||
|
|
||
| const ids = rows.data?.value?.map((r: any) => r.id) ?? []; | ||
| let updated = 0; | ||
| for (const id of ids) { | ||
| const res = await fetch(`/${model}('${id}')`, { | ||
| method: "PATCH", | ||
| body: update, | ||
| }); | ||
| if (!res.error) updated++; | ||
| } | ||
| return updated as any; | ||
| }, | ||
| }; | ||
| }, | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.