@proofkit/better-auth 0.2.3 → 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.d.ts +0 -2
- package/dist/esm/cli/index.js +6 -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 +5 -5
- package/src/adapter.ts +125 -61
- package/src/cli/index.ts +4 -2
- package/src/migrate.ts +29 -7
- package/src/odata/index.ts +229 -68
package/dist/esm/odata/index.js
CHANGED
|
@@ -1,36 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { logger } from "@better-fetch/logger";
|
|
3
|
-
import { logger as logger$1 } from "better-auth";
|
|
1
|
+
import { logger } from "better-auth";
|
|
4
2
|
import { ok, err } from "neverthrow";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
input: z.object({ tableName: z.string(), fields: z.array(z.any()) })
|
|
12
|
-
},
|
|
13
|
-
/**
|
|
14
|
-
* Add fields to a table
|
|
15
|
-
*/
|
|
16
|
-
"@patch/FileMaker_Tables/:tableName": {
|
|
17
|
-
params: z.object({ tableName: z.string() }),
|
|
18
|
-
input: z.object({ fields: z.array(z.any()) })
|
|
19
|
-
},
|
|
20
|
-
/**
|
|
21
|
-
* Delete a table
|
|
22
|
-
*/
|
|
23
|
-
"@delete/FileMaker_Tables/:tableName": {
|
|
24
|
-
params: z.object({ tableName: z.string() })
|
|
25
|
-
},
|
|
26
|
-
/**
|
|
27
|
-
* Delete a field from a table
|
|
28
|
-
*/
|
|
29
|
-
"@delete/FileMaker_Tables/:tableName/:fieldName": {
|
|
30
|
-
params: z.object({ tableName: z.string(), fieldName: z.string() })
|
|
3
|
+
function validateUrl(input) {
|
|
4
|
+
try {
|
|
5
|
+
const url = new URL(input);
|
|
6
|
+
return ok(url);
|
|
7
|
+
} catch (error) {
|
|
8
|
+
return err(error);
|
|
31
9
|
}
|
|
32
|
-
}
|
|
33
|
-
function
|
|
10
|
+
}
|
|
11
|
+
function createRawFetch(args) {
|
|
34
12
|
const result = validateUrl(args.serverUrl);
|
|
35
13
|
if (result.isErr()) {
|
|
36
14
|
throw new Error("Invalid server URL");
|
|
@@ -40,44 +18,182 @@ function createFmOdataFetch(args) {
|
|
|
40
18
|
baseURL += `/otto`;
|
|
41
19
|
}
|
|
42
20
|
baseURL += `/fmi/odata/v4/${args.database}`;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
},
|
|
50
|
-
onError: (error) => {
|
|
51
|
-
console.error("url", error.request.url.toString());
|
|
52
|
-
console.log(error.error);
|
|
53
|
-
console.log("error.request.body", JSON.stringify(error.request.body));
|
|
54
|
-
},
|
|
55
|
-
schema,
|
|
56
|
-
plugins: [
|
|
57
|
-
logger({
|
|
58
|
-
verbose: args.logging === "verbose",
|
|
59
|
-
enabled: args.logging === "verbose" || !!args.logging,
|
|
60
|
-
console: {
|
|
61
|
-
fail: (...args2) => logger$1.error("better-fetch", ...args2),
|
|
62
|
-
success: (...args2) => logger$1.info("better-fetch", ...args2),
|
|
63
|
-
log: (...args2) => logger$1.info("better-fetch", ...args2),
|
|
64
|
-
error: (...args2) => logger$1.error("better-fetch", ...args2),
|
|
65
|
-
warn: (...args2) => logger$1.warn("better-fetch", ...args2)
|
|
66
|
-
}
|
|
67
|
-
})
|
|
68
|
-
]
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
function validateUrl(input) {
|
|
72
|
-
try {
|
|
73
|
-
const url = new URL(input);
|
|
74
|
-
return ok(url);
|
|
75
|
-
} catch (error) {
|
|
76
|
-
return err(error);
|
|
21
|
+
const authHeaders = {};
|
|
22
|
+
if ("apiKey" in args.auth) {
|
|
23
|
+
authHeaders.Authorization = `Bearer ${args.auth.apiKey}`;
|
|
24
|
+
} else {
|
|
25
|
+
const credentials = btoa(`${args.auth.username}:${args.auth.password}`);
|
|
26
|
+
authHeaders.Authorization = `Basic ${credentials}`;
|
|
77
27
|
}
|
|
28
|
+
const wrappedFetch = async (input, options) => {
|
|
29
|
+
try {
|
|
30
|
+
let url;
|
|
31
|
+
if (typeof input === "string") {
|
|
32
|
+
url = input.startsWith("http") ? input : `${baseURL}${input.startsWith("/") ? input : `/${input}`}`;
|
|
33
|
+
} else if (input instanceof URL) {
|
|
34
|
+
url = input.toString();
|
|
35
|
+
} else if (input instanceof Request) {
|
|
36
|
+
url = input.url;
|
|
37
|
+
} else {
|
|
38
|
+
url = String(input);
|
|
39
|
+
}
|
|
40
|
+
let processedBody = options == null ? void 0 : options.body;
|
|
41
|
+
if (processedBody && typeof processedBody === "object" && !(processedBody instanceof FormData) && !(processedBody instanceof URLSearchParams) && !(processedBody instanceof ReadableStream)) {
|
|
42
|
+
processedBody = JSON.stringify(processedBody);
|
|
43
|
+
}
|
|
44
|
+
const headers = {
|
|
45
|
+
"Content-Type": "application/json",
|
|
46
|
+
...authHeaders,
|
|
47
|
+
...(options == null ? void 0 : options.headers) || {}
|
|
48
|
+
};
|
|
49
|
+
const requestInit = {
|
|
50
|
+
...options,
|
|
51
|
+
headers,
|
|
52
|
+
body: processedBody
|
|
53
|
+
};
|
|
54
|
+
if (args.logging === "verbose" || args.logging === true) {
|
|
55
|
+
logger.info(
|
|
56
|
+
"raw-fetch",
|
|
57
|
+
`${requestInit.method || "GET"} ${url}`
|
|
58
|
+
);
|
|
59
|
+
if (requestInit.body) {
|
|
60
|
+
logger.info("raw-fetch", "Request body:", requestInit.body);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const response = await fetch(url, requestInit);
|
|
64
|
+
if (args.logging === "verbose" || args.logging === true) {
|
|
65
|
+
logger.info(
|
|
66
|
+
"raw-fetch",
|
|
67
|
+
`Response status: ${response.status} ${response.statusText}`
|
|
68
|
+
);
|
|
69
|
+
logger.info(
|
|
70
|
+
"raw-fetch",
|
|
71
|
+
`Response headers:`,
|
|
72
|
+
Object.fromEntries(response.headers.entries())
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
if (!response.ok) {
|
|
76
|
+
const errorText = await response.text().catch(() => "Unknown error");
|
|
77
|
+
if (args.logging === "verbose" || args.logging === true) {
|
|
78
|
+
logger.error(
|
|
79
|
+
"raw-fetch",
|
|
80
|
+
`HTTP Error ${response.status}: ${errorText}`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
error: `HTTP ${response.status}: ${errorText}`,
|
|
85
|
+
response
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
let responseData;
|
|
89
|
+
const contentType = response.headers.get("content-type");
|
|
90
|
+
if (args.logging === "verbose" || args.logging === true) {
|
|
91
|
+
logger.info(
|
|
92
|
+
"raw-fetch",
|
|
93
|
+
`Response content-type: ${contentType || "none"}`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
if (contentType == null ? void 0 : contentType.includes("application/json")) {
|
|
97
|
+
try {
|
|
98
|
+
const responseText = await response.text();
|
|
99
|
+
if (args.logging === "verbose" || args.logging === true) {
|
|
100
|
+
logger.info(
|
|
101
|
+
"raw-fetch",
|
|
102
|
+
`Raw response text: "${responseText}"`
|
|
103
|
+
);
|
|
104
|
+
logger.info(
|
|
105
|
+
"raw-fetch",
|
|
106
|
+
`Response text length: ${responseText.length}`
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
if (responseText.trim() === "") {
|
|
110
|
+
if (args.logging === "verbose" || args.logging === true) {
|
|
111
|
+
logger.info(
|
|
112
|
+
"raw-fetch",
|
|
113
|
+
"Empty JSON response, returning null"
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
responseData = null;
|
|
117
|
+
} else {
|
|
118
|
+
responseData = JSON.parse(responseText);
|
|
119
|
+
if (args.logging === "verbose" || args.logging === true) {
|
|
120
|
+
logger.info(
|
|
121
|
+
"raw-fetch",
|
|
122
|
+
"Successfully parsed JSON response"
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
} catch (parseError) {
|
|
127
|
+
if (args.logging === "verbose" || args.logging === true) {
|
|
128
|
+
logger.error(
|
|
129
|
+
"raw-fetch",
|
|
130
|
+
"JSON parse error:",
|
|
131
|
+
parseError
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
error: `Failed to parse JSON response: ${parseError instanceof Error ? parseError.message : "Unknown parse error"}`,
|
|
136
|
+
response
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
} else if (contentType == null ? void 0 : contentType.includes("text/")) {
|
|
140
|
+
responseData = await response.text();
|
|
141
|
+
if (args.logging === "verbose" || args.logging === true) {
|
|
142
|
+
logger.info(
|
|
143
|
+
"raw-fetch",
|
|
144
|
+
`Text response: "${responseData}"`
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
} else {
|
|
148
|
+
try {
|
|
149
|
+
responseData = await response.text();
|
|
150
|
+
if (args.logging === "verbose" || args.logging === true) {
|
|
151
|
+
logger.info(
|
|
152
|
+
"raw-fetch",
|
|
153
|
+
`Unknown content-type response as text: "${responseData}"`
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
} catch {
|
|
157
|
+
responseData = null;
|
|
158
|
+
if (args.logging === "verbose" || args.logging === true) {
|
|
159
|
+
logger.info(
|
|
160
|
+
"raw-fetch",
|
|
161
|
+
"Could not parse response as text, returning null"
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (options == null ? void 0 : options.output) {
|
|
167
|
+
const validation = options.output.safeParse(responseData);
|
|
168
|
+
if (validation.success) {
|
|
169
|
+
return {
|
|
170
|
+
data: validation.data,
|
|
171
|
+
response
|
|
172
|
+
};
|
|
173
|
+
} else {
|
|
174
|
+
return {
|
|
175
|
+
error: `Validation failed: ${validation.error.message}`,
|
|
176
|
+
response
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
data: responseData,
|
|
182
|
+
response
|
|
183
|
+
};
|
|
184
|
+
} catch (error) {
|
|
185
|
+
return {
|
|
186
|
+
error: error instanceof Error ? error.message : "Unknown error occurred"
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
return {
|
|
191
|
+
baseURL,
|
|
192
|
+
fetch: wrappedFetch
|
|
193
|
+
};
|
|
78
194
|
}
|
|
79
195
|
export {
|
|
80
|
-
|
|
196
|
+
createRawFetch,
|
|
81
197
|
validateUrl
|
|
82
198
|
};
|
|
83
199
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../../src/odata/index.ts"],"sourcesContent":["import { createFetch, createSchema } from \"@better-fetch/fetch\";\nimport { logger } from \"@better-fetch/logger\";\nimport { logger as betterAuthLogger } from \"better-auth\";\nimport { err, ok, Result } from \"neverthrow\";\nimport { z } from \"zod/v4\";\n\ntype BasicAuthCredentials = {\n username: string;\n password: string;\n};\ntype OttoAPIKeyAuth = {\n apiKey: string;\n};\ntype ODataAuth = BasicAuthCredentials | OttoAPIKeyAuth;\n\nexport type FmOdataConfig = {\n serverUrl: string;\n auth: ODataAuth;\n database: string;\n logging?: true | \"verbose\" | \"none\";\n};\n\nconst schema = createSchema({\n /**\n * Create a new table\n */\n \"@post/FileMaker_Tables\": {\n input: z.object({ tableName: z.string(), fields: z.array(z.any()) }),\n },\n /**\n * Add fields to a table\n */\n \"@patch/FileMaker_Tables/:tableName\": {\n params: z.object({ tableName: z.string() }),\n input: z.object({ fields: z.array(z.any()) }),\n },\n /**\n * Delete a table\n */\n \"@delete/FileMaker_Tables/:tableName\": {\n params: z.object({ tableName: z.string() }),\n },\n /**\n * Delete a field from a table\n */\n \"@delete/FileMaker_Tables/:tableName/:fieldName\": {\n params: z.object({ tableName: z.string(), fieldName: z.string() }),\n },\n});\n\nexport function createFmOdataFetch(args: FmOdataConfig) {\n const result = validateUrl(args.serverUrl);\n\n if (result.isErr()) {\n throw new Error(\"Invalid server URL\");\n }\n let baseURL = result.value.origin;\n if (\"apiKey\" in args.auth) {\n baseURL += `/otto`;\n }\n baseURL += `/fmi/odata/v4/${args.database}`;\n\n return createFetch({\n baseURL,\n auth:\n \"apiKey\" in args.auth\n ? { type: \"Bearer\", token: args.auth.apiKey }\n : {\n type: \"Basic\",\n username: args.auth.username,\n password: args.auth.password,\n },\n onError: (error) => {\n console.error(\"url\", error.request.url.toString());\n console.log(error.error);\n console.log(\"error.request.body\", JSON.stringify(error.request.body));\n },\n schema,\n plugins: [\n logger({\n verbose: args.logging === \"verbose\",\n enabled: args.logging === \"verbose\" || !!args.logging,\n console: {\n fail: (...args) => betterAuthLogger.error(\"better-fetch\", ...args),\n success: (...args) => betterAuthLogger.info(\"better-fetch\", ...args),\n log: (...args) => betterAuthLogger.info(\"better-fetch\", ...args),\n error: (...args) => betterAuthLogger.error(\"better-fetch\", ...args),\n warn: (...args) => betterAuthLogger.warn(\"better-fetch\", ...args),\n },\n }),\n ],\n });\n}\n\nexport function validateUrl(input: string): Result<URL, unknown> {\n try {\n const url = new URL(input);\n return ok(url);\n } catch (error) {\n return err(error);\n }\n}\n"],"names":["args","betterAuthLogger"],"mappings":";;;;;AAsBA,MAAM,SAAS,aAAa;AAAA;AAAA;AAAA;AAAA,EAI1B,0BAA0B;AAAA,IACxB,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,UAAU,QAAQ,EAAE,MAAM,EAAE,IAAK,CAAA,EAAG,CAAA;AAAA,EACrE;AAAA;AAAA;AAAA;AAAA,EAIA,sCAAsC;AAAA,IACpC,QAAQ,EAAE,OAAO,EAAE,WAAW,EAAE,OAAA,GAAU;AAAA,IAC1C,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAA,CAAK,EAAG,CAAA;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA,EAIA,uCAAuC;AAAA,IACrC,QAAQ,EAAE,OAAO,EAAE,WAAW,EAAE,SAAU,CAAA;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA,EAIA,kDAAkD;AAAA,IAChD,QAAQ,EAAE,OAAO,EAAE,WAAW,EAAE,OAAO,GAAG,WAAW,EAAE,SAAU,CAAA;AAAA,EAAA;AAErE,CAAC;AAEM,SAAS,mBAAmB,MAAqB;AAChD,QAAA,SAAS,YAAY,KAAK,SAAS;AAErC,MAAA,OAAO,SAAS;AACZ,UAAA,IAAI,MAAM,oBAAoB;AAAA,EAAA;AAElC,MAAA,UAAU,OAAO,MAAM;AACvB,MAAA,YAAY,KAAK,MAAM;AACd,eAAA;AAAA,EAAA;AAEF,aAAA,iBAAiB,KAAK,QAAQ;AAEzC,SAAO,YAAY;AAAA,IACjB;AAAA,IACA,MACE,YAAY,KAAK,OACb,EAAE,MAAM,UAAU,OAAO,KAAK,KAAK,OAAA,IACnC;AAAA,MACE,MAAM;AAAA,MACN,UAAU,KAAK,KAAK;AAAA,MACpB,UAAU,KAAK,KAAK;AAAA,IACtB;AAAA,IACN,SAAS,CAAC,UAAU;AAClB,cAAQ,MAAM,OAAO,MAAM,QAAQ,IAAI,UAAU;AACzC,cAAA,IAAI,MAAM,KAAK;AACvB,cAAQ,IAAI,sBAAsB,KAAK,UAAU,MAAM,QAAQ,IAAI,CAAC;AAAA,IACtE;AAAA,IACA;AAAA,IACA,SAAS;AAAA,MACP,OAAO;AAAA,QACL,SAAS,KAAK,YAAY;AAAA,QAC1B,SAAS,KAAK,YAAY,aAAa,CAAC,CAAC,KAAK;AAAA,QAC9C,SAAS;AAAA,UACP,MAAM,IAAIA,UAASC,SAAiB,MAAM,gBAAgB,GAAGD,KAAI;AAAA,UACjE,SAAS,IAAIA,UAASC,SAAiB,KAAK,gBAAgB,GAAGD,KAAI;AAAA,UACnE,KAAK,IAAIA,UAASC,SAAiB,KAAK,gBAAgB,GAAGD,KAAI;AAAA,UAC/D,OAAO,IAAIA,UAASC,SAAiB,MAAM,gBAAgB,GAAGD,KAAI;AAAA,UAClE,MAAM,IAAIA,UAASC,SAAiB,KAAK,gBAAgB,GAAGD,KAAI;AAAA,QAAA;AAAA,MAEnE,CAAA;AAAA,IAAA;AAAA,EACH,CACD;AACH;AAEO,SAAS,YAAY,OAAqC;AAC3D,MAAA;AACI,UAAA,MAAM,IAAI,IAAI,KAAK;AACzB,WAAO,GAAG,GAAG;AAAA,WACN,OAAO;AACd,WAAO,IAAI,KAAK;AAAA,EAAA;AAEpB;"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../src/odata/index.ts"],"sourcesContent":["import { logger as betterAuthLogger } from \"better-auth\";\nimport { err, ok, Result } from \"neverthrow\";\nimport { z } from \"zod/v4\";\n\ntype BasicAuthCredentials = {\n username: string;\n password: string;\n};\ntype OttoAPIKeyAuth = {\n apiKey: string;\n};\ntype ODataAuth = BasicAuthCredentials | OttoAPIKeyAuth;\n\nexport type FmOdataConfig = {\n serverUrl: string;\n auth: ODataAuth;\n database: string;\n logging?: true | \"verbose\" | \"none\";\n};\n\nexport function validateUrl(input: string): Result<URL, unknown> {\n try {\n const url = new URL(input);\n return ok(url);\n } catch (error) {\n return err(error);\n }\n}\n\nexport function createRawFetch(args: FmOdataConfig) {\n const result = validateUrl(args.serverUrl);\n\n if (result.isErr()) {\n throw new Error(\"Invalid server URL\");\n }\n\n let baseURL = result.value.origin;\n if (\"apiKey\" in args.auth) {\n baseURL += `/otto`;\n }\n baseURL += `/fmi/odata/v4/${args.database}`;\n\n // Create authentication headers\n const authHeaders: Record<string, string> = {};\n if (\"apiKey\" in args.auth) {\n authHeaders.Authorization = `Bearer ${args.auth.apiKey}`;\n } else {\n const credentials = btoa(`${args.auth.username}:${args.auth.password}`);\n authHeaders.Authorization = `Basic ${credentials}`;\n }\n\n // Enhanced fetch function with body handling, validation, and structured responses\n const wrappedFetch = async <TOutput = any>(\n input: string | URL | Request,\n options?: Omit<RequestInit, \"body\"> & {\n body?: any; // Allow any type for body\n output?: z.ZodSchema<TOutput>; // Optional schema for validation\n },\n ): Promise<{ data?: TOutput; error?: string; response?: Response }> => {\n try {\n let url: string;\n\n // Handle different input types\n if (typeof input === \"string\") {\n // If it's already a full URL, use as-is, otherwise prepend baseURL\n url = input.startsWith(\"http\")\n ? input\n : `${baseURL}${input.startsWith(\"/\") ? input : `/${input}`}`;\n } else if (input instanceof URL) {\n url = input.toString();\n } else if (input instanceof Request) {\n url = input.url;\n } else {\n url = String(input);\n }\n\n // Handle body serialization\n let processedBody = options?.body;\n if (\n processedBody &&\n typeof processedBody === \"object\" &&\n !(processedBody instanceof FormData) &&\n !(processedBody instanceof URLSearchParams) &&\n !(processedBody instanceof ReadableStream)\n ) {\n processedBody = JSON.stringify(processedBody);\n }\n\n // Merge headers\n const headers = {\n \"Content-Type\": \"application/json\",\n ...authHeaders,\n ...(options?.headers || {}),\n };\n\n const requestInit: RequestInit = {\n ...options,\n headers,\n body: processedBody,\n };\n\n // Optional logging\n if (args.logging === \"verbose\" || args.logging === true) {\n betterAuthLogger.info(\n \"raw-fetch\",\n `${requestInit.method || \"GET\"} ${url}`,\n );\n if (requestInit.body) {\n betterAuthLogger.info(\"raw-fetch\", \"Request body:\", requestInit.body);\n }\n }\n\n const response = await fetch(url, requestInit);\n\n // Optional logging for response details\n if (args.logging === \"verbose\" || args.logging === true) {\n betterAuthLogger.info(\n \"raw-fetch\",\n `Response status: ${response.status} ${response.statusText}`,\n );\n betterAuthLogger.info(\n \"raw-fetch\",\n `Response headers:`,\n Object.fromEntries(response.headers.entries()),\n );\n }\n\n // Check if response is ok\n if (!response.ok) {\n const errorText = await response.text().catch(() => \"Unknown error\");\n if (args.logging === \"verbose\" || args.logging === true) {\n betterAuthLogger.error(\n \"raw-fetch\",\n `HTTP Error ${response.status}: ${errorText}`,\n );\n }\n return {\n error: `HTTP ${response.status}: ${errorText}`,\n response,\n };\n }\n\n // Parse response based on content type\n let responseData: any;\n const contentType = response.headers.get(\"content-type\");\n\n if (args.logging === \"verbose\" || args.logging === true) {\n betterAuthLogger.info(\n \"raw-fetch\",\n `Response content-type: ${contentType || \"none\"}`,\n );\n }\n\n if (contentType?.includes(\"application/json\")) {\n try {\n const responseText = await response.text();\n if (args.logging === \"verbose\" || args.logging === true) {\n betterAuthLogger.info(\n \"raw-fetch\",\n `Raw response text: \"${responseText}\"`,\n );\n betterAuthLogger.info(\n \"raw-fetch\",\n `Response text length: ${responseText.length}`,\n );\n }\n\n // Handle empty responses\n if (responseText.trim() === \"\") {\n if (args.logging === \"verbose\" || args.logging === true) {\n betterAuthLogger.info(\n \"raw-fetch\",\n \"Empty JSON response, returning null\",\n );\n }\n responseData = null;\n } else {\n responseData = JSON.parse(responseText);\n if (args.logging === \"verbose\" || args.logging === true) {\n betterAuthLogger.info(\n \"raw-fetch\",\n \"Successfully parsed JSON response\",\n );\n }\n }\n } catch (parseError) {\n if (args.logging === \"verbose\" || args.logging === true) {\n betterAuthLogger.error(\n \"raw-fetch\",\n \"JSON parse error:\",\n parseError,\n );\n }\n return {\n error: `Failed to parse JSON response: ${parseError instanceof Error ? parseError.message : \"Unknown parse error\"}`,\n response,\n };\n }\n } else if (contentType?.includes(\"text/\")) {\n // Handle text responses (text/plain, text/html, etc.)\n responseData = await response.text();\n if (args.logging === \"verbose\" || args.logging === true) {\n betterAuthLogger.info(\n \"raw-fetch\",\n `Text response: \"${responseData}\"`,\n );\n }\n } else {\n // For other content types, try to get text but don't fail if it's binary\n try {\n responseData = await response.text();\n if (args.logging === \"verbose\" || args.logging === true) {\n betterAuthLogger.info(\n \"raw-fetch\",\n `Unknown content-type response as text: \"${responseData}\"`,\n );\n }\n } catch {\n // If text parsing fails (e.g., binary data), return null\n responseData = null;\n if (args.logging === \"verbose\" || args.logging === true) {\n betterAuthLogger.info(\n \"raw-fetch\",\n \"Could not parse response as text, returning null\",\n );\n }\n }\n }\n\n // Validate output if schema provided\n if (options?.output) {\n const validation = options.output.safeParse(responseData);\n if (validation.success) {\n return {\n data: validation.data,\n response,\n };\n } else {\n return {\n error: `Validation failed: ${validation.error.message}`,\n response,\n };\n }\n }\n\n // Return unvalidated data\n return {\n data: responseData as TOutput,\n response,\n };\n } catch (error) {\n return {\n error:\n error instanceof Error ? error.message : \"Unknown error occurred\",\n };\n }\n };\n\n return {\n baseURL,\n fetch: wrappedFetch,\n };\n}\n"],"names":["betterAuthLogger"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@proofkit/better-auth",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "FileMaker adapter for Better Auth",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/esm/index.js",
|
|
@@ -38,8 +38,6 @@
|
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@babel/preset-react": "^7.27.1",
|
|
40
40
|
"@babel/preset-typescript": "^7.27.1",
|
|
41
|
-
"@better-fetch/fetch": "1.1.17",
|
|
42
|
-
"@better-fetch/logger": "^1.1.18",
|
|
43
41
|
"@commander-js/extra-typings": "^14.0.0",
|
|
44
42
|
"@tanstack/vite-config": "^0.2.0",
|
|
45
43
|
"better-auth": "^1.2.10",
|
|
@@ -49,6 +47,7 @@
|
|
|
49
47
|
"dotenv": "^16.5.0",
|
|
50
48
|
"fs-extra": "^11.3.0",
|
|
51
49
|
"neverthrow": "^8.2.0",
|
|
50
|
+
"odata-query": "^8.0.4",
|
|
52
51
|
"prompts": "^2.4.2",
|
|
53
52
|
"vite": "^6.3.4",
|
|
54
53
|
"zod": "3.25.64"
|
|
@@ -56,10 +55,11 @@
|
|
|
56
55
|
"devDependencies": {
|
|
57
56
|
"@types/fs-extra": "^11.0.4",
|
|
58
57
|
"@types/prompts": "^2.4.9",
|
|
58
|
+
"@vitest/ui": "^3.2.4",
|
|
59
59
|
"fm-odata-client": "^3.0.1",
|
|
60
60
|
"publint": "^0.3.12",
|
|
61
|
-
"typescript": "^5.
|
|
62
|
-
"vitest": "^3.2.
|
|
61
|
+
"typescript": "^5.9.2",
|
|
62
|
+
"vitest": "^3.2.4"
|
|
63
63
|
},
|
|
64
64
|
"scripts": {
|
|
65
65
|
"dev": "pnpm build:watch",
|
package/src/adapter.ts
CHANGED
|
@@ -3,9 +3,10 @@ import {
|
|
|
3
3
|
createAdapter,
|
|
4
4
|
type AdapterDebugLogs,
|
|
5
5
|
} from "better-auth/adapters";
|
|
6
|
-
import {
|
|
6
|
+
import { createRawFetch, type FmOdataConfig } from "./odata";
|
|
7
7
|
import { prettifyError, z } from "zod/v4";
|
|
8
8
|
import { logger } from "better-auth";
|
|
9
|
+
import buildQuery from "odata-query";
|
|
9
10
|
|
|
10
11
|
const configSchema = z.object({
|
|
11
12
|
debugLogs: z.unknown().optional(),
|
|
@@ -153,7 +154,7 @@ export const FileMakerAdapter = (
|
|
|
153
154
|
}
|
|
154
155
|
const config = parsed.data;
|
|
155
156
|
|
|
156
|
-
const fetch =
|
|
157
|
+
const { fetch, baseURL } = createRawFetch({
|
|
157
158
|
...config.odata,
|
|
158
159
|
logging: config.debugLogs ? "verbose" : "none",
|
|
159
160
|
});
|
|
@@ -192,114 +193,177 @@ export const FileMakerAdapter = (
|
|
|
192
193
|
count: async ({ model, where }) => {
|
|
193
194
|
const filter = parseWhere(where);
|
|
194
195
|
logger.debug("$filter", filter);
|
|
195
|
-
|
|
196
|
+
|
|
197
|
+
const query = buildQuery({
|
|
198
|
+
filter: filter.length > 0 ? filter : undefined,
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const result = await fetch(`/${model}/$count${query}`, {
|
|
196
202
|
method: "GET",
|
|
197
|
-
query: {
|
|
198
|
-
$filter: filter,
|
|
199
|
-
},
|
|
200
203
|
output: z.object({ value: z.number() }),
|
|
201
204
|
});
|
|
202
205
|
if (!result.data) {
|
|
203
206
|
throw new Error("Failed to count records");
|
|
204
207
|
}
|
|
205
|
-
return result.data?.value ?? 0;
|
|
208
|
+
return (result.data?.value as any) ?? 0;
|
|
206
209
|
},
|
|
207
210
|
findOne: async ({ model, where }) => {
|
|
208
211
|
const filter = parseWhere(where);
|
|
209
212
|
logger.debug("$filter", filter);
|
|
210
|
-
|
|
213
|
+
|
|
214
|
+
const query = buildQuery({
|
|
215
|
+
top: 1,
|
|
216
|
+
filter: filter.length > 0 ? filter : undefined,
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
const result = await fetch(`/${model}${query}`, {
|
|
211
220
|
method: "GET",
|
|
212
|
-
query: {
|
|
213
|
-
...(filter.length > 0 ? { $filter: filter } : {}),
|
|
214
|
-
$top: 1,
|
|
215
|
-
},
|
|
216
221
|
output: z.object({ value: z.array(z.any()) }),
|
|
217
222
|
});
|
|
218
223
|
if (result.error) {
|
|
219
224
|
throw new Error("Failed to find record");
|
|
220
225
|
}
|
|
221
|
-
return result.data?.value?.[0] ?? null;
|
|
226
|
+
return (result.data?.value?.[0] as any) ?? null;
|
|
222
227
|
},
|
|
223
228
|
findMany: async ({ model, where, limit, offset, sortBy }) => {
|
|
224
229
|
const filter = parseWhere(where);
|
|
225
|
-
logger.debug("
|
|
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);
|
|
226
241
|
|
|
227
|
-
const
|
|
242
|
+
const result = await fetch(`/${model}${query}`, {
|
|
228
243
|
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
|
-
},
|
|
237
244
|
output: z.object({ value: z.array(z.any()) }),
|
|
238
245
|
});
|
|
239
|
-
|
|
246
|
+
logger.debug("RESULT", result);
|
|
247
|
+
|
|
248
|
+
if (result.error) {
|
|
240
249
|
throw new Error("Failed to find records");
|
|
241
250
|
}
|
|
242
|
-
|
|
251
|
+
|
|
252
|
+
return (result.data?.value as any) ?? [];
|
|
243
253
|
},
|
|
244
254
|
delete: async ({ model, where }) => {
|
|
245
255
|
const filter = parseWhere(where);
|
|
256
|
+
console.log("DELETE", { model, where, filter });
|
|
246
257
|
logger.debug("$filter", filter);
|
|
247
|
-
|
|
248
|
-
|
|
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}')`, {
|
|
249
278
|
method: "DELETE",
|
|
250
|
-
query: {
|
|
251
|
-
...(where.length > 0 ? { $filter: filter } : {}),
|
|
252
|
-
$top: 1,
|
|
253
|
-
},
|
|
254
279
|
});
|
|
255
280
|
if (result.error) {
|
|
281
|
+
console.log("DELETE ERROR", result.error);
|
|
256
282
|
throw new Error("Failed to delete record");
|
|
257
283
|
}
|
|
258
284
|
},
|
|
259
285
|
deleteMany: async ({ model, where }) => {
|
|
260
286
|
const filter = parseWhere(where);
|
|
261
|
-
|
|
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 });
|
|
267
288
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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,
|
|
274
293
|
});
|
|
275
|
-
|
|
276
|
-
|
|
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++;
|
|
277
307
|
}
|
|
278
|
-
return
|
|
308
|
+
return deleted;
|
|
279
309
|
},
|
|
280
310
|
update: async ({ model, where, update }) => {
|
|
281
|
-
const
|
|
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}')`, {
|
|
282
330
|
method: "PATCH",
|
|
283
|
-
query: {
|
|
284
|
-
...(where.length > 0 ? { $filter: parseWhere(where) } : {}),
|
|
285
|
-
$top: 1,
|
|
286
|
-
$select: [`"id"`],
|
|
287
|
-
},
|
|
288
331
|
body: update,
|
|
289
|
-
output: z.object({ value: z.array(z.any()) }),
|
|
290
332
|
});
|
|
291
|
-
|
|
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;
|
|
292
343
|
},
|
|
293
344
|
updateMany: async ({ model, where, update }) => {
|
|
294
345
|
const filter = parseWhere(where);
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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,
|
|
301
350
|
});
|
|
302
|
-
|
|
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;
|
|
303
367
|
},
|
|
304
368
|
};
|
|
305
369
|
},
|
package/src/cli/index.ts
CHANGED
|
@@ -13,7 +13,8 @@ import { logger } from "better-auth";
|
|
|
13
13
|
import prompts from "prompts";
|
|
14
14
|
import chalk from "chalk";
|
|
15
15
|
import { AdapterOptions } from "../adapter";
|
|
16
|
-
import {
|
|
16
|
+
import { createRawFetch } from "../odata";
|
|
17
|
+
import "dotenv/config";
|
|
17
18
|
|
|
18
19
|
async function main() {
|
|
19
20
|
const program = new Command();
|
|
@@ -63,7 +64,7 @@ async function main() {
|
|
|
63
64
|
const betterAuthSchema = getAuthTables(config);
|
|
64
65
|
|
|
65
66
|
const adapterConfig = (adapter.options as AdapterOptions).config;
|
|
66
|
-
const fetch =
|
|
67
|
+
const { fetch } = createRawFetch({
|
|
67
68
|
...adapterConfig.odata,
|
|
68
69
|
auth:
|
|
69
70
|
// If the username and password are provided in the CLI, use them to authenticate instead of what's in the config file.
|
|
@@ -73,6 +74,7 @@ async function main() {
|
|
|
73
74
|
password: options.password,
|
|
74
75
|
}
|
|
75
76
|
: adapterConfig.odata.auth,
|
|
77
|
+
logging: "verbose", // Enable logging for CLI operations
|
|
76
78
|
});
|
|
77
79
|
|
|
78
80
|
const migrationPlan = await planMigration(
|